diff --git a/api.go b/api.go index 5dfbdcb..72b354e 100644 --- a/api.go +++ b/api.go @@ -1540,6 +1540,8 @@ func (app *appContext) GetConfig(gc *gin.Context) { resp.Sections["email"].Settings["language"] = el resp.Sections["password_resets"].Settings["language"] = pl resp.Sections["telegram"].Settings["language"] = tl + resp.Sections["discord"].Settings["language"] = tl + resp.Sections["matrix"].Settings["language"] = tl gc.JSON(200, resp) } @@ -2365,6 +2367,40 @@ func (app *appContext) MatrixCheckPIN(gc *gin.Context) { 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). // @Produce json // @Success 200 {object} DiscordUsersDTO diff --git a/config.go b/config.go index 70e1330..e0e9ba3 100644 --- a/config.go +++ b/config.go @@ -84,6 +84,8 @@ func (app *appContext) loadConfig() error { app.MustSetValue("user_expiry", "email_html", "jfa-go:"+"user-expired.html") 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("device").SetValue("jfa-go") app.config.Section("jellyfin").Key("device_id").SetValue(fmt.Sprintf("jfa-go-%s-%s", version, commit)) diff --git a/config/config-base.json b/config/config-base.json index 46d2b18..7088ffb 100644 --- a/config/config-base.json +++ b/config/config-base.json @@ -727,6 +727,15 @@ "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, diff --git a/matrix.go b/matrix.go index 3360abc..0eb8095 100644 --- a/matrix.go +++ b/matrix.go @@ -145,12 +145,20 @@ func (d *MatrixDaemon) commandLang(event *gomatrix.Event, code, lang string) { } } -func (d *MatrixDaemon) SendStart(userID string) (ok bool) { +func (d *MatrixDaemon) CreateRoom(userID string) (string, error) { room, err := d.bot.CreateRoom(&gomatrix.ReqCreateRoom{ Visibility: "private", Invite: []string{userID}, - Topic: "jfa-go", + 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 @@ -160,13 +168,13 @@ func (d *MatrixDaemon) SendStart(userID string) (ok bool) { d.tokens[pin] = UnverifiedUser{ false, &MatrixUser{ - RoomID: room.RoomID, + RoomID: roomID, UserID: userID, Lang: lang, }, } _, err = d.bot.SendText( - room.RoomID, + 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"}), ) diff --git a/models.go b/models.go index f75eefb..07f2d95 100644 --- a/models.go +++ b/models.go @@ -290,6 +290,12 @@ type DiscordInviteDTO struct { 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"` +} diff --git a/router.go b/router.go index 9b14672..d8b61fd 100644 --- a/router.go +++ b/router.go @@ -130,6 +130,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) { 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 { diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts index 33a1b14..17d8e06 100644 --- a/ts/modules/accounts.ts +++ b/ts/modules/accounts.ts @@ -186,8 +186,11 @@ class user implements User { this._matrixID = u; if (!u) { this._notifyDropdown.querySelector(".accounts-area-matrix").classList.add("unfocused"); - this._matrix.innerHTML = `${window.lang.strings("add")}`; - // (this._matrix.querySelector("span") as HTMLSpanElement).onclick = this._addMatrix; + this._matrix.innerHTML = ` + ${window.lang.strings("add")} + + `; + (this._matrix.querySelector("span") as HTMLSpanElement).onclick = this._addMatrix; } else { this._notifyDropdown.querySelector(".accounts-area-matrix").classList.remove("unfocused"); this._matrix.innerHTML = ` @@ -200,7 +203,37 @@ class user implements User { } } } - + + 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 = ``; + 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) {