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) {