feat: Add Nextcloud Talk notification provider (#6158)

Signed-off-by: Marcel Müller <marcel-mueller@gmx.de>
Co-authored-by: Frank Elsinga <frank@elsinga.de>
This commit is contained in:
Marcel Müller 2025-10-04 12:24:41 +02:00 committed by GitHub
parent b398c8080e
commit 796b342ca5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 143 additions and 1 deletions

View File

@ -0,0 +1,66 @@
const { UP, DOWN } = require("../../src/util");
const Crypto = require("crypto");
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class NextcloudTalk extends NotificationProvider {
name = "nextcloudtalk";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
// See documentation at https://nextcloud-talk.readthedocs.io/en/latest/bots/#sending-a-chat-message
const okMsg = "Sent Successfully.";
// Create a random string
const talkRandom = encodeURIComponent(
Crypto
.randomBytes(64)
.toString("hex")
.slice(0, 64)
);
// Create the signature over random and message
const talkSignature = Crypto
.createHmac("sha256", Buffer.from(notification.botSecret, "utf8"))
.update(Buffer.from(`${talkRandom}${msg}`, "utf8"))
.digest("hex");
let silentUp = (heartbeatJSON?.status === UP && notification.sendSilentUp);
let silentDown = (heartbeatJSON?.status === DOWN && notification.sendSilentDown);
let silent = (silentUp || silentDown);
let url = `${notification.host}/ocs/v2.php/apps/spreed/api/v1/bot/${notification.conversationToken}/message`;
let config = this.getAxiosConfigWithProxy({});
const data = {
message: msg,
silent
};
const options = {
...config,
headers: {
"X-Nextcloud-Talk-Bot-Random": talkRandom,
"X-Nextcloud-Talk-Bot-Signature": talkSignature,
"OCS-APIRequest": true,
}
};
try {
let result = await axios.post(url, data, options);
if (result?.status === 201) {
return okMsg;
}
throw new Error("Nextcloud Talk Error " + (result?.status ?? "Unknown"));
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = NextcloudTalk;

View File

@ -28,6 +28,7 @@ const LineNotify = require("./notification-providers/linenotify");
const LunaSea = require("./notification-providers/lunasea");
const Matrix = require("./notification-providers/matrix");
const Mattermost = require("./notification-providers/mattermost");
const NextcloudTalk = require("./notification-providers/nextcloudtalk");
const Nostr = require("./notification-providers/nostr");
const Ntfy = require("./notification-providers/ntfy");
const Octopush = require("./notification-providers/octopush");
@ -123,6 +124,7 @@ class Notification {
new LunaSea(),
new Matrix(),
new Mattermost(),
new NextcloudTalk(),
new Nostr(),
new Ntfy(),
new Octopush(),

View File

@ -132,6 +132,7 @@ export default {
"lunasea": "LunaSea",
"matrix": "Matrix",
"mattermost": "Mattermost",
"nextcloudtalk": "Nextcloud Talk",
"nostr": "Nostr",
"ntfy": "Ntfy",
"octopush": "Octopush",

View File

@ -0,0 +1,65 @@
<template>
<div class="mb-3">
<div class="mb-3">
<label for="nextcloud-host" class="form-label">{{ $t("Nextcloud host") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="nextcloud-host" v-model="$parent.notification.host" type="text" class="form-control">
</div>
<div class="mb-3">
<label for="nextcloud-conversation-token" class="form-label">{{ $t("Conversation token") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="nextcloud-conversation-token" v-model="$parent.notification.conversationToken" type="text" class="form-control">
</div>
<div class="mb-3">
<label for="nextcloud-bot-secret" class="form-label">{{ $t("Bot secret") }}<span style="color: red;"><sup>*</sup></span></label>
<HiddenInput id="nextcloud-bot-secret" v-model="$parent.notification.botSecret" :required="true" autocomplete="new-password"></HiddenInput>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input v-model="$parent.notification.sendSilentUp" class="form-check-input" type="checkbox">
<label class="form-check-label">{{ $t("Send UP silently") }}</label>
</div>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input v-model="$parent.notification.sendSilentDown" class="form-check-input" type="checkbox">
<label class="form-check-label">{{ $t("Send DOWN silently") }}</label>
</div>
</div>
<div class="mb-3">
<div class="form-text">
<p>{{ $t("Installing a Nextcloud Talk bot requires administrative access to the server.") }}</p>
<p>{{ $t("Example:") }}</p>
<pre># Setup bot
occ talk:bot:install \
--feature response --no-setup \
"Uptime Bot" "Secret" \
https://uptime.domain.invalid
# Obtain BotId
occ talk:bot:list
# Add bot to a conversation
occ talk:bot:setup &lt;BotId&gt; &lt;ConversationToken&gt;
</pre>
<i18n-t tag="p" keypath="Read more:">
<a href="https://nextcloud-talk.readthedocs.io/en/latest/bot-list/#uptime-kuma" target="_blank">https://nextcloud-talk.readthedocs.io</a>
</i18n-t>
</div>
</div>
</div>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
};
</script>

View File

@ -27,6 +27,7 @@ import LineNotify from "./LineNotify.vue";
import LunaSea from "./LunaSea.vue";
import Matrix from "./Matrix.vue";
import Mattermost from "./Mattermost.vue";
import NextcloudTalk from "./NextcloudTalk.vue";
import Nostr from "./Nostr.vue";
import Ntfy from "./Ntfy.vue";
import Octopush from "./Octopush.vue";
@ -110,6 +111,7 @@ const NotificationFormList = {
"lunasea": LunaSea,
"matrix": Matrix,
"mattermost": Mattermost,
"nextcloudtalk": NextcloudTalk,
"nostr": Nostr,
"ntfy": Ntfy,
"octopush": Octopush,

View File

@ -1152,5 +1152,11 @@
"Staged Tags for Batch Add": "Staged Tags for Batch Add",
"Clear Form": "Clear Form",
"pause": "Pause",
"Manual": "Manual"
"Manual": "Manual",
"Nextcloud host": "Nextcloud host",
"Conversation token": "Conversation token",
"Bot secret": "Bot secret",
"Send UP silently": "Send UP silently",
"Send DOWN silently": "Send DOWN silently",
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Installing a Nextcloud Talk bot requires administrative access to the server."
}