ThirdPartyServices: SetContactMethods for setting, enable/disable

replace AddContactMethods with a more generalised SetContactMethods,
which can set (or leave alone) contact method addresses/names/ids and
set (or leave alone) contact preferences. A little awkward to use, but
works everywhere, and now a bunch of Jellyseerr integration features are
present for Ombi (which they should've been anyway).
This commit is contained in:
Harvey Tindall
2025-11-26 20:57:11 +00:00
parent 1d4ea7d0a0
commit 27a80734f9
14 changed files with 232 additions and 108 deletions

View File

@@ -6,6 +6,7 @@ import (
"strconv" "strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/common"
"github.com/hrfee/jfa-go/jellyseerr" "github.com/hrfee/jfa-go/jellyseerr"
lm "github.com/hrfee/jfa-go/logmessages" lm "github.com/hrfee/jfa-go/logmessages"
) )
@@ -124,29 +125,62 @@ func (js *JellyseerrWrapper) ImportUser(jellyfinID string, req newUserDTO, profi
return return
} }
func (js *JellyseerrWrapper) AddContactMethods(jellyfinID string, req newUserDTO, discord *DiscordUser, telegram *TelegramUser) (err error) { func (js *JellyseerrWrapper) SetContactMethods(jellyfinID string, email *string, discord *DiscordUser, telegram *TelegramUser, contactPrefs *common.ContactPreferences) (err error) {
_, err = js.MustGetUser(jellyfinID) _, err = js.MustGetUser(jellyfinID)
if err != nil { if err != nil {
return return
} }
contactMethods := map[jellyseerr.NotificationsField]any{} if contactPrefs == nil {
if emailEnabled { contactPrefs = &common.ContactPreferences{
err = js.ModifyMainUserSettings(jellyfinID, jellyseerr.MainUserSettings{Email: req.Email}) Email: nil,
if err != nil { Discord: nil,
// FIXME: This is a little ugly, considering all other errors are unformatted Telegram: nil,
err = fmt.Errorf(lm.FailedSetEmailAddress, lm.Jellyseerr, jellyfinID, err) Matrix: nil,
return
} else {
contactMethods[jellyseerr.FieldEmailEnabled] = req.EmailContact
} }
} }
if discordEnabled && discord != nil { contactMethods := map[jellyseerr.NotificationsField]any{}
contactMethods[jellyseerr.FieldDiscord] = discord.ID if emailEnabled {
contactMethods[jellyseerr.FieldDiscordEnabled] = req.DiscordContact if contactPrefs.Email != nil {
contactMethods[jellyseerr.FieldEmailEnabled] = *(contactPrefs.Email)
} else if email != nil && *email != "" {
contactMethods[jellyseerr.FieldEmailEnabled] = true
}
if email != nil {
err = js.ModifyMainUserSettings(jellyfinID, jellyseerr.MainUserSettings{Email: *email})
if err != nil {
// FIXME: This is a little ugly, considering all other errors are unformatted
err = fmt.Errorf(lm.FailedSetEmailAddress, lm.Jellyseerr, jellyfinID, err)
return
}
}
} }
if telegramEnabled && telegram != nil { if discordEnabled {
contactMethods[jellyseerr.FieldTelegram] = telegram.ChatID if contactPrefs.Discord != nil {
contactMethods[jellyseerr.FieldTelegramEnabled] = req.TelegramContact contactMethods[jellyseerr.FieldDiscordEnabled] = *(contactPrefs.Discord)
} else if discord != nil && discord.ID != "" {
contactMethods[jellyseerr.FieldDiscordEnabled] = true
}
if discord != nil {
contactMethods[jellyseerr.FieldDiscord] = discord.ID
// Whether this is still necessary or not, i don't know.
if discord.ID == "" {
contactMethods[jellyseerr.FieldDiscord] = jellyseerr.BogusIdentifier
}
}
}
if telegramEnabled {
if contactPrefs.Telegram != nil {
contactMethods[jellyseerr.FieldTelegramEnabled] = *(contactPrefs.Telegram)
} else if telegram != nil && telegram.ChatID != 0 {
contactMethods[jellyseerr.FieldTelegramEnabled] = true
}
if telegram != nil {
contactMethods[jellyseerr.FieldTelegram] = telegram.ChatID
// Whether this is still necessary or not, i don't know.
if telegram.ChatID == 0 {
contactMethods[jellyseerr.FieldTelegram] = jellyseerr.BogusIdentifier
}
}
} }
if len(contactMethods) > 0 { if len(contactMethods) > 0 {
err = js.ModifyNotifications(jellyfinID, contactMethods) err = js.ModifyNotifications(jellyfinID, contactMethods)

View File

@@ -4,7 +4,7 @@ import (
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/jellyseerr" "github.com/hrfee/jfa-go/common"
lm "github.com/hrfee/jfa-go/logmessages" lm "github.com/hrfee/jfa-go/logmessages"
"github.com/lithammer/shortuuid/v3" "github.com/lithammer/shortuuid/v3"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
@@ -263,21 +263,21 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) {
} }
app.storage.SetTelegramKey(req.ID, tgUser) app.storage.SetTelegramKey(req.ID, tgUser)
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{ for _, tps := range app.thirdPartyServices {
jellyseerr.FieldTelegram: tgUser.ChatID, if err := tps.SetContactMethods(req.ID, nil, nil, &tgUser, &common.ContactPreferences{
jellyseerr.FieldTelegramEnabled: tgUser.Contact, Telegram: &tgUser.Contact,
}); err != nil { }); err != nil {
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err) app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
}
} }
linkExistingOmbiDiscordTelegram(app)
app.InvalidateWebUserCache() app.InvalidateWebUserCache()
respondBool(200, true, gc) respondBool(200, true, gc)
} }
// @Summary Sets whether to notify a user through telegram/discord/matrix/email or not. // @Summary Sets whether to notify a user through telegram/discord/matrix/email or not.
// @Produce json // @Produce json
// @Param SetContactMethodsDTO body SetContactMethodsDTO true "User's Jellyfin ID and whether or not to notify then through Telegram." // @Param SetContactPreferencesDTO body SetContactPreferencesDTO true "User's Jellyfin ID and whether or not to notify then through Telegram."
// @Success 200 {object} boolResponse // @Success 200 {object} boolResponse
// @Success 400 {object} boolResponse // @Success 400 {object} boolResponse
// @Success 500 {object} boolResponse // @Success 500 {object} boolResponse
@@ -285,24 +285,24 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) {
// @Security Bearer // @Security Bearer
// @tags Other // @tags Other
func (app *appContext) SetContactMethods(gc *gin.Context) { func (app *appContext) SetContactMethods(gc *gin.Context) {
var req SetContactMethodsDTO var req SetContactPreferencesDTO
gc.BindJSON(&req) gc.BindJSON(&req)
if req.ID == "" { if req.ID == "" {
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
app.setContactMethods(req, gc) app.setContactPreferences(req, gc)
} }
func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Context) { func (app *appContext) setContactPreferences(req SetContactPreferencesDTO, gc *gin.Context) {
jsPrefs := map[jellyseerr.NotificationsField]any{} contactPrefs := common.ContactPreferences{}
if tgUser, ok := app.storage.GetTelegramKey(req.ID); ok { if tgUser, ok := app.storage.GetTelegramKey(req.ID); ok {
change := tgUser.Contact != req.Telegram change := tgUser.Contact != req.Telegram
tgUser.Contact = req.Telegram tgUser.Contact = req.Telegram
app.storage.SetTelegramKey(req.ID, tgUser) app.storage.SetTelegramKey(req.ID, tgUser)
if change { if change {
app.debug.Printf(lm.SetContactPrefForService, lm.Telegram, tgUser.Username, req.Telegram) app.debug.Printf(lm.SetContactPrefForService, lm.Telegram, tgUser.Username, req.Telegram)
jsPrefs[jellyseerr.FieldTelegramEnabled] = req.Telegram contactPrefs.Telegram = &req.Telegram
} }
} }
if dcUser, ok := app.storage.GetDiscordKey(req.ID); ok { if dcUser, ok := app.storage.GetDiscordKey(req.ID); ok {
@@ -311,7 +311,7 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
app.storage.SetDiscordKey(req.ID, dcUser) app.storage.SetDiscordKey(req.ID, dcUser)
if change { if change {
app.debug.Printf(lm.SetContactPrefForService, lm.Discord, dcUser.Username, req.Discord) app.debug.Printf(lm.SetContactPrefForService, lm.Discord, dcUser.Username, req.Discord)
jsPrefs[jellyseerr.FieldDiscordEnabled] = req.Discord contactPrefs.Discord = &req.Discord
} }
} }
if mxUser, ok := app.storage.GetMatrixKey(req.ID); ok { if mxUser, ok := app.storage.GetMatrixKey(req.ID); ok {
@@ -320,6 +320,7 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
app.storage.SetMatrixKey(req.ID, mxUser) app.storage.SetMatrixKey(req.ID, mxUser)
if change { if change {
app.debug.Printf(lm.SetContactPrefForService, lm.Matrix, mxUser.UserID, req.Matrix) app.debug.Printf(lm.SetContactPrefForService, lm.Matrix, mxUser.UserID, req.Matrix)
contactPrefs.Matrix = &req.Matrix
} }
} }
if email, ok := app.storage.GetEmailsKey(req.ID); ok { if email, ok := app.storage.GetEmailsKey(req.ID); ok {
@@ -328,13 +329,13 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
app.storage.SetEmailsKey(req.ID, email) app.storage.SetEmailsKey(req.ID, email)
if change { if change {
app.debug.Printf(lm.SetContactPrefForService, lm.Email, email.Addr, req.Email) app.debug.Printf(lm.SetContactPrefForService, lm.Email, email.Addr, req.Email)
jsPrefs[jellyseerr.FieldEmailEnabled] = req.Email contactPrefs.Email = &req.Email
} }
} }
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
err := app.js.ModifyNotifications(req.ID, jsPrefs) for _, tps := range app.thirdPartyServices {
if err != nil { if err := tps.SetContactMethods(req.ID, nil, nil, nil, &contactPrefs); err != nil {
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err) app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
} }
} }
app.InvalidateWebUserCache() app.InvalidateWebUserCache()
@@ -621,11 +622,12 @@ func (app *appContext) DiscordConnect(gc *gin.Context) {
app.storage.SetDiscordKey(req.JellyfinID, user) app.storage.SetDiscordKey(req.JellyfinID, user)
if err := app.js.ModifyNotifications(req.JellyfinID, map[jellyseerr.NotificationsField]any{ for _, tps := range app.thirdPartyServices {
jellyseerr.FieldDiscord: req.DiscordID, if err := tps.SetContactMethods(req.JellyfinID, nil, &user, nil, &common.ContactPreferences{
jellyseerr.FieldDiscordEnabled: true, Discord: &user.Contact,
}); err != nil { }); err != nil {
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err) app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
}
} }
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
@@ -659,12 +661,14 @@ func (app *appContext) UnlinkDiscord(gc *gin.Context) {
} */ } */
app.storage.DeleteDiscordKey(req.ID) app.storage.DeleteDiscordKey(req.ID)
// FIXME: Use thirdPartyServices for this contact := false
if err := app.js.ModifyNotifications(req.ID, map[jellyseerr.NotificationsField]any{
jellyseerr.FieldDiscord: jellyseerr.BogusIdentifier, for _, tps := range app.thirdPartyServices {
jellyseerr.FieldDiscordEnabled: false, if err := tps.SetContactMethods(req.ID, nil, EmptyDiscordUser(), nil, &common.ContactPreferences{
}); err != nil { Discord: &contact,
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err) }); err != nil {
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
}
} }
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
@@ -697,12 +701,14 @@ func (app *appContext) UnlinkTelegram(gc *gin.Context) {
} */ } */
app.storage.DeleteTelegramKey(req.ID) app.storage.DeleteTelegramKey(req.ID)
// FIXME: Use thirdPartyServices for this contact := false
if err := app.js.ModifyNotifications(req.ID, map[jellyseerr.NotificationsField]any{
jellyseerr.FieldTelegram: jellyseerr.BogusIdentifier, for _, tps := range app.thirdPartyServices {
jellyseerr.FieldTelegramEnabled: false, if err := tps.SetContactMethods(req.ID, nil, nil, EmptyTelegramUser(), &common.ContactPreferences{
}); err != nil { Telegram: &contact,
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err) }); err != nil {
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
}
} }
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{

View File

@@ -8,7 +8,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/common" "github.com/hrfee/jfa-go/common"
lm "github.com/hrfee/jfa-go/logmessages" lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/jfa-go/ombi" ombiLib "github.com/hrfee/jfa-go/ombi"
"github.com/hrfee/mediabrowser" "github.com/hrfee/mediabrowser"
) )
@@ -147,7 +147,8 @@ func (app *appContext) DeleteOmbiProfile(gc *gin.Context) {
} }
type OmbiWrapper struct { type OmbiWrapper struct {
*ombi.Ombi OmbiUserByJfID func(jfID string) (map[string]interface{}, error)
*ombiLib.Ombi
} }
func (ombi *OmbiWrapper) applyProfile(user map[string]interface{}, profile map[string]interface{}) (err error) { func (ombi *OmbiWrapper) applyProfile(user map[string]interface{}, profile map[string]interface{}) (err error) {
@@ -189,23 +190,69 @@ func (ombi *OmbiWrapper) ImportUser(jellyfinID string, req newUserDTO, profile P
return return
} }
func (ombi *OmbiWrapper) AddContactMethods(jellyfinID string, req newUserDTO, discord *DiscordUser, telegram *TelegramUser) (err error) { func (ombi *OmbiWrapper) SetContactMethods(jellyfinID string, email *string, discord *DiscordUser, telegram *TelegramUser, contactPrefs *common.ContactPreferences) (err error) {
var ombiUser map[string]interface{} ombiUser, err := ombi.OmbiUserByJfID(jellyfinID)
ombiUser, err = ombi.getUser(req.Username, req.Email)
if err != nil { if err != nil {
return return
} }
if discordEnabled || telegramEnabled { if contactPrefs == nil {
dID := "" contactPrefs = &common.ContactPreferences{
tUser := "" Email: nil,
Discord: nil,
Telegram: nil,
Matrix: nil,
}
}
if emailEnabled && email != nil {
ombiUser["emailAddress"] = *email
err = ombi.ModifyUser(ombiUser)
if err != nil {
// FIXME: This is a little ugly, considering all other errors are unformatted
err = fmt.Errorf(lm.FailedSetEmailAddress, lm.Ombi, jellyfinID, err)
return
}
}
data := make([]ombiLib.NotificationPref, 0, 2)
if discordEnabled {
pref := ombiLib.NotificationPref{
Agent: ombiLib.NotifAgentDiscord,
UserID: ombiUser["id"].(string),
}
valid := false
if contactPrefs.Discord != nil {
pref.Enabled = *(contactPrefs.Discord)
valid = true
} else if discord != nil && discord.ID != "" {
pref.Enabled = true
valid = true
}
if discord != nil { if discord != nil {
dID = discord.ID pref.Value = discord.ID
valid = true
}
if valid {
data = append(data, pref)
}
}
if telegramEnabled && telegram != nil {
pref := ombiLib.NotificationPref{
Agent: ombiLib.NotifAgentTelegram,
UserID: ombiUser["id"].(string),
}
if contactPrefs.Telegram != nil {
pref.Enabled = *(contactPrefs.Telegram)
} else if telegram != nil && telegram.Username != "" {
pref.Enabled = true
} }
if telegram != nil { if telegram != nil {
tUser = telegram.Username pref.Value = telegram.Username
} }
data = append(data, pref)
}
if len(data) > 0 {
var resp string var resp string
resp, err = ombi.SetNotificationPrefs(ombiUser, dID, tUser) resp, err = ombi.SetNotificationPrefs(ombiUser, data)
if err != nil { if err != nil {
if resp != "" { if resp != "" {
err = fmt.Errorf("%v, %s", err, resp) err = fmt.Errorf("%v, %s", err, resp)

View File

@@ -107,7 +107,7 @@ func (app *appContext) MyDetails(gc *gin.Context) {
// @Summary Sets whether to notify yourself through telegram/discord/matrix/email or not. // @Summary Sets whether to notify yourself through telegram/discord/matrix/email or not.
// @Produce json // @Produce json
// @Param SetContactMethodsDTO body SetContactMethodsDTO true "User's Jellyfin ID and whether or not to notify then through Telegram." // @Param SetContactPreferencesDTO body SetContactPreferencesDTO true "User's Jellyfin ID and whether or not to notify then through Telegram."
// @Success 200 {object} boolResponse // @Success 200 {object} boolResponse
// @Success 400 {object} boolResponse // @Success 400 {object} boolResponse
// @Success 500 {object} boolResponse // @Success 500 {object} boolResponse
@@ -115,14 +115,14 @@ func (app *appContext) MyDetails(gc *gin.Context) {
// @Security Bearer // @Security Bearer
// @tags User Page // @tags User Page
func (app *appContext) SetMyContactMethods(gc *gin.Context) { func (app *appContext) SetMyContactMethods(gc *gin.Context) {
var req SetContactMethodsDTO var req SetContactPreferencesDTO
gc.BindJSON(&req) gc.BindJSON(&req)
req.ID = gc.GetString("jfId") req.ID = gc.GetString("jfId")
if req.ID == "" { if req.ID == "" {
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
app.setContactMethods(req, gc) app.setContactPreferences(req, gc)
} }
// @Summary Logout by deleting refresh token from cookies. // @Summary Logout by deleting refresh token from cookies.

View File

@@ -10,7 +10,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/hrfee/jfa-go/jellyseerr" "github.com/hrfee/jfa-go/common"
lm "github.com/hrfee/jfa-go/logmessages" lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser" "github.com/hrfee/mediabrowser"
"github.com/lithammer/shortuuid/v3" "github.com/lithammer/shortuuid/v3"
@@ -54,12 +54,13 @@ func (app *appContext) NewUserFromAdmin(gc *gin.Context) {
nu.Log() nu.Log()
} }
var emailStore *EmailAddress = nil
if emailEnabled && req.Email != "" { if emailEnabled && req.Email != "" {
emailStore := EmailAddress{ emailStore = &EmailAddress{
Addr: req.Email, Addr: req.Email,
Contact: true, Contact: true,
} }
app.storage.SetEmailsKey(nu.User.ID, emailStore) app.storage.SetEmailsKey(nu.User.ID, *emailStore)
} }
for _, tps := range app.thirdPartyServices { for _, tps := range app.thirdPartyServices {
@@ -67,7 +68,12 @@ func (app *appContext) NewUserFromAdmin(gc *gin.Context) {
continue continue
} }
// We only have email // We only have email
err := tps.AddContactMethods(nu.User.ID, req, nil, nil) if emailStore == nil {
continue
}
err := tps.SetContactMethods(nu.User.ID, &req.Email, nil, nil, &common.ContactPreferences{
Email: &(emailStore.Contact),
})
if err != nil { if err != nil {
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err) app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
} }
@@ -279,12 +285,14 @@ func (app *appContext) PostNewUserFromInvite(nu NewUserData, req ConfirmationKey
referralsEnabled := profile != nil && profile.ReferralTemplateKey != "" && app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false) referralsEnabled := profile != nil && profile.ReferralTemplateKey != "" && app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false)
contactPrefs := common.ContactPreferences{}
if (emailEnabled && req.Email != "") || invite.UserLabel != "" || referralsEnabled { if (emailEnabled && req.Email != "") || invite.UserLabel != "" || referralsEnabled {
emailStore := EmailAddress{ emailStore := EmailAddress{
Addr: req.Email, Addr: req.Email,
Contact: (req.Email != ""), Contact: (req.Email != ""),
Label: invite.UserLabel, Label: invite.UserLabel,
} }
contactPrefs.Email = &(emailStore.Contact)
if profile != nil { if profile != nil {
profile.ReferralTemplateKey = profile.ReferralTemplateKey profile.ReferralTemplateKey = profile.ReferralTemplateKey
} }
@@ -345,18 +353,22 @@ func (app *appContext) PostNewUserFromInvite(nu NewUserData, req ConfirmationKey
var discordUser *DiscordUser = nil var discordUser *DiscordUser = nil
var telegramUser *TelegramUser = nil var telegramUser *TelegramUser = nil
// FIXME: Make sure its okay to, then change this check to len(app.tps) != 0 && (for loop of tps.Enabled )
if app.ombi.Enabled(app, profile) || app.js.Enabled(app, profile) { if app.ombi.Enabled(app, profile) || app.js.Enabled(app, profile) {
// FIXME: figure these out in a nicer way? this relies on the current ordering, // FIXME: figure these out in a nicer way? this relies on the current ordering,
// which may not be fixed. // which may not be fixed.
if discordEnabled { if discordEnabled {
if req.completeContactMethods[0].User != nil { if req.completeContactMethods[0].User != nil {
discordUser = req.completeContactMethods[0].User.(*DiscordUser) discordUser = req.completeContactMethods[0].User.(*DiscordUser)
contactPrefs.Discord = &discordUser.Contact
} }
if telegramEnabled && req.completeContactMethods[1].User != nil { if telegramEnabled && req.completeContactMethods[1].User != nil {
telegramUser = req.completeContactMethods[1].User.(*TelegramUser) telegramUser = req.completeContactMethods[1].User.(*TelegramUser)
contactPrefs.Telegram = &telegramUser.Contact
} }
} else if telegramEnabled && req.completeContactMethods[0].User != nil { } else if telegramEnabled && req.completeContactMethods[0].User != nil {
telegramUser = req.completeContactMethods[0].User.(*TelegramUser) telegramUser = req.completeContactMethods[0].User.(*TelegramUser)
contactPrefs.Telegram = &telegramUser.Contact
} }
} }
@@ -365,7 +377,7 @@ func (app *appContext) PostNewUserFromInvite(nu NewUserData, req ConfirmationKey
continue continue
} }
// User already created, now we can link contact methods // User already created, now we can link contact methods
err := tps.AddContactMethods(nu.User.ID, req.newUserDTO, discordUser, telegramUser) err := tps.SetContactMethods(nu.User.ID, &(req.Email), discordUser, telegramUser, &contactPrefs)
if err != nil { if err != nil {
app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err) app.err.Printf(lm.FailedSyncContactMethods, tps.Name(), err)
} }
@@ -1119,39 +1131,21 @@ func (app *appContext) ModifyLabels(gc *gin.Context) {
} }
func (app *appContext) modifyEmail(jfID string, addr string) { func (app *appContext) modifyEmail(jfID string, addr string) {
contactPrefChanged := false
emailStore, ok := app.storage.GetEmailsKey(jfID) emailStore, ok := app.storage.GetEmailsKey(jfID)
// Auto enable contact by email for newly added addresses // Auto enable contact by email for newly added addresses
if !ok || emailStore.Addr == "" { if !ok || emailStore.Addr == "" {
emailStore = EmailAddress{ emailStore = EmailAddress{
Contact: true, Contact: true,
} }
contactPrefChanged = true
} }
emailStore.Addr = addr emailStore.Addr = addr
app.storage.SetEmailsKey(jfID, emailStore) app.storage.SetEmailsKey(jfID, emailStore)
if app.config.Section("ombi").Key("enabled").MustBool(false) {
ombiUser, err := app.getOmbiUser(jfID) for _, tps := range app.thirdPartyServices {
if err == nil { if err := tps.SetContactMethods(jfID, &addr, nil, nil, &common.ContactPreferences{
ombiUser["emailAddress"] = addr Email: &(emailStore.Contact),
err = app.ombi.ModifyUser(ombiUser) }); err != nil {
if err != nil { app.err.Printf(lm.FailedSetEmailAddress, tps.Name(), jfID, err)
app.err.Printf(lm.FailedSetEmailAddress, lm.Ombi, jfID, err)
}
}
}
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
err := app.js.ModifyMainUserSettings(jfID, jellyseerr.MainUserSettings{Email: addr})
if err != nil {
app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, jfID, err)
} else if contactPrefChanged {
contactMethods := map[jellyseerr.NotificationsField]any{
jellyseerr.FieldEmailEnabled: true,
}
err := app.js.ModifyNotifications(jfID, contactMethods)
if err != nil {
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
}
} }
} }
app.InvalidateWebUserCache() app.InvalidateWebUserCache()

View File

@@ -16,6 +16,16 @@ import (
lm "github.com/hrfee/jfa-go/logmessages" lm "github.com/hrfee/jfa-go/logmessages"
) )
const (
BogusIdentifier = "123412341234123456"
)
// ContactPreferences holds whether or not a user should be contacted through each of the available
// methods. If nil, leave setting alone.
type ContactPreferences struct {
Email, Discord, Telegram, Matrix *bool
}
// TimeoutHandler recovers from an http timeout or panic. // TimeoutHandler recovers from an http timeout or panic.
type TimeoutHandler func() type TimeoutHandler func()

View File

@@ -32,6 +32,17 @@ type DiscordDaemon struct {
retryOpts *common.MustAuthenticateOptions retryOpts *common.MustAuthenticateOptions
} }
func EmptyDiscordUser() *DiscordUser {
return &DiscordUser{
ID: "",
Username: "",
Discriminator: "",
Lang: "",
Contact: false,
JellyfinID: "",
}
}
func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) { func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
token := app.config.Section("discord").Key("token").String() token := app.config.Section("discord").Key("token").String()
if token == "" { if token == "" {

View File

@@ -369,7 +369,9 @@ func start(asDaemon, firstCall bool) {
// NOTE: As of writing this, the order in app.thirdPartyServices doesn't matter, // NOTE: As of writing this, the order in app.thirdPartyServices doesn't matter,
// but in future it might (like app.contactMethods does), so append to the end! // but in future it might (like app.contactMethods does), so append to the end!
if app.config.Section("ombi").Key("enabled").MustBool(false) { if app.config.Section("ombi").Key("enabled").MustBool(false) {
app.ombi = &OmbiWrapper{} app.ombi = &OmbiWrapper{
OmbiUserByJfID: app.getOmbiUser,
}
app.debug.Printf(lm.UsingOmbi) app.debug.Printf(lm.UsingOmbi)
ombiServer := app.config.Section("ombi").Key("server").String() ombiServer := app.config.Section("ombi").Key("server").String()
app.ombi.Ombi = ombi.NewOmbi( app.ombi.Ombi = ombi.NewOmbi(

View File

@@ -64,6 +64,16 @@ var matrixFilter = mautrix.Filter{
}, },
} }
func EmptyMatrixUser() *MatrixUser {
return &MatrixUser{
RoomID: "",
UserID: "",
Lang: "",
Contact: false,
JellyfinID: "",
}
}
func (d *MatrixDaemon) renderUserID(uid id.UserID) id.UserID { func (d *MatrixDaemon) renderUserID(uid id.UserID) id.UserID {
if uid[0] != '@' { if uid[0] != '@' {
uid = "@" + uid uid = "@" + uid

View File

@@ -6,6 +6,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/hrfee/jfa-go/ombi"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
) )
@@ -191,7 +192,10 @@ func linkExistingOmbiDiscordTelegram(app *appContext) error {
app.debug.Printf("Failed to get Ombi user with Discord/Telegram \"%s\"/\"%s\": %v", ids[0], ids[1], err) app.debug.Printf("Failed to get Ombi user with Discord/Telegram \"%s\"/\"%s\": %v", ids[0], ids[1], err)
continue continue
} }
_, err = app.ombi.SetNotificationPrefs(ombiUser, ids[0], ids[1]) _, err = app.ombi.SetNotificationPrefs(ombiUser, []ombi.NotificationPref{
{ombi.NotifAgentDiscord, ombiUser["id"].(string), ids[0], true},
{ombi.NotifAgentTelegram, ombiUser["id"].(string), ids[1], true},
})
if err != nil { if err != nil {
app.debug.Printf("Failed to set prefs for Ombi user \"%s\": %v", ombiUser["userName"].(string), err) app.debug.Printf("Failed to set prefs for Ombi user \"%s\": %v", ombiUser["userName"].(string), err)
continue continue

View File

@@ -278,7 +278,7 @@ type telegramSetDTO struct {
ID string `json:"id"` // Jellyfin ID of user. ID string `json:"id"` // Jellyfin ID of user.
} }
type SetContactMethodsDTO struct { type SetContactPreferencesDTO struct {
ID string `json:"id"` ID string `json:"id"`
Email bool `json:"email"` Email bool `json:"email"`
Discord bool `json:"discord"` Discord bool `json:"discord"`

View File

@@ -246,16 +246,8 @@ type NotificationPref struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
} }
func (ombi *Ombi) SetNotificationPrefs(user map[string]interface{}, discordID, telegramUser string) (result string, err error) { func (ombi *Ombi) SetNotificationPrefs(user map[string]interface{}, data []NotificationPref) (result string, err error) {
id := user["id"].(string)
url := fmt.Sprintf("%s/api/v1/Identity/NotificationPreferences", ombi.server) url := fmt.Sprintf("%s/api/v1/Identity/NotificationPreferences", ombi.server)
data := []NotificationPref{}
if discordID != "" {
data = append(data, NotificationPref{NotifAgentDiscord, id, discordID, true})
}
if telegramUser != "" {
data = append(data, NotificationPref{NotifAgentTelegram, id, telegramUser, true})
}
var code int var code int
result, code, err = ombi.send("POST", url, data, true, map[string]string{"UserName": user["userName"].(string)}) result, code, err = ombi.send("POST", url, data, true, map[string]string{"UserName": user["userName"].(string)})
err = co.GenericErr(code, err) err = co.GenericErr(code, err)

View File

@@ -625,7 +625,11 @@ type ThirdPartyService interface {
common.ConfigurableTransport common.ConfigurableTransport
// ok implies user imported, err can be any issue that occurs during // ok implies user imported, err can be any issue that occurs during
ImportUser(jellyfinID string, req newUserDTO, profile Profile) (err error, ok bool) ImportUser(jellyfinID string, req newUserDTO, profile Profile) (err error, ok bool)
AddContactMethods(jellyfinID string, req newUserDTO, discord *DiscordUser, telegram *TelegramUser) (err error) // SetContactMethods allows setting any combination of contact method address/username/id and contact preference. To leave fields alone, pass nil pointers, to set them to a blank value, use "" or Empty(Discord|Telegram|Matrix)User(). Ignores the "Contact" field in some xyzUser structs.
SetContactMethods(jellyfinID string, email *string, discord *DiscordUser, telegram *TelegramUser, contactPrefs *common.ContactPreferences) (err error)
// Enabled returns whether this service is enabled in the given profile.
// Not for checking if the service is enabled in general!
// If it wasn't, it wouldn't be in app.thirdPartyServices.
Enabled(app *appContext, profile *Profile) bool Enabled(app *appContext, profile *Profile) bool
Name() string Name() string
} }

View File

@@ -28,6 +28,16 @@ func (tv TelegramVerifiedToken) ToUser() *TelegramUser {
} }
} }
func EmptyTelegramUser() *TelegramUser {
return &TelegramUser{
JellyfinID: "",
ChatID: 0,
Username: "",
Lang: "",
Contact: false,
}
}
func (t *TelegramVerifiedToken) Name() string { return t.Username } func (t *TelegramVerifiedToken) Name() string { return t.Username }
func (t *TelegramVerifiedToken) SetMethodID(id any) { t.ChatID = id.(int64) } func (t *TelegramVerifiedToken) SetMethodID(id any) { t.ChatID = id.(int64) }
func (t *TelegramVerifiedToken) MethodID() any { return t.ChatID } func (t *TelegramVerifiedToken) MethodID() any { return t.ChatID }