feat: add Ooredoo (Maldives) SMS notification provider (#7571)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Mifzaal Abdul Baari
2026-07-05 18:48:27 +05:00
committed by GitHub
parent ffa791da8b
commit 8854e00a92
6 changed files with 164 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
// The Ooredoo bulk_sms/v2 gateway accepts at most this many recipients per
// request, so larger recipient lists are split into batches of this size.
const MAX_RECIPIENTS_PER_REQUEST = 20;
class Ooredoo extends NotificationProvider {
name = "Ooredoo";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully.";
const url = notification.ooredooServerUrl || "https://o-papi1-lb01.ooredoo.mv/bulk_sms/v2";
// Users may enter recipients separated by comma, semicolon, space or
// newline.
const recipients = notification.ooredooToNumber
.split(/[\s,;]+/)
.map((number) => this.normalizePhoneNumber(number))
.filter((number) => number !== "");
if (recipients.length === 0) {
throw new Error("No valid recipient phone number was provided.");
}
try {
let config = {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: "Bearer " + notification.ooredooBearerToken,
},
};
config = this.getAxiosConfigWithProxy(config);
// The gateway only accepts MAX_RECIPIENTS_PER_REQUEST numbers per
// call, so send the recipients in batches of that size.
for (let i = 0; i < recipients.length; i += MAX_RECIPIENTS_PER_REQUEST) {
const batch = recipients.slice(i, i + MAX_RECIPIENTS_PER_REQUEST);
const data = new URLSearchParams({
username: notification.ooredooUsername,
// The gateway expects the access key to be Base64 encoded.
access_key: Buffer.from(notification.ooredooAccessKey).toString("base64"),
message: msg,
batch: batch.join(" "),
});
const resp = await axios.post(url, data.toString(), config);
// The gateway returns HTTP 200 even on failure; the real outcome
// is carried in "response_code" (0 means the batch was accepted).
if (!resp.data || Number(resp.data.response_code) !== 0) {
const reason =
resp.data && resp.data.response_message
? resp.data.response_message
: "response_code=" + (resp.data ? resp.data.response_code : "unknown");
throw new Error("Ooredoo rejected the message: " + reason);
}
}
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
/**
* Normalize a Maldivian phone number to the "960XXXXXXX" format expected
* by the Ooredoo gateway. Numbers already in that form are returned as is.
* @param {string} phoneNumber The phone number to normalize
* @returns {string} The normalized phone number, or "" if it is empty
*/
normalizePhoneNumber(phoneNumber) {
const number = phoneNumber.replace(/[\s+]/g, "");
if (number === "") {
return "";
}
// A bare 7-digit local number gets the Maldives (960) country code.
if (/^\d{7}$/.test(number)) {
return "960" + number;
}
return number;
}
}
module.exports = Ooredoo;
+2
View File
@@ -37,6 +37,7 @@ const Ntfy = require("./notification-providers/ntfy");
const Octopush = require("./notification-providers/octopush");
const OneChat = require("./notification-providers/onechat");
const OneBot = require("./notification-providers/onebot");
const Ooredoo = require("./notification-providers/ooredoo");
const Opsgenie = require("./notification-providers/opsgenie");
const JiraServiceManagement = require("./notification-providers/jira-service-management");
const PagerDuty = require("./notification-providers/pagerduty");
@@ -150,6 +151,7 @@ class Notification {
new OneChat(),
new OneBot(),
new Onesender(),
new Ooredoo(),
new Opsgenie(),
new JiraServiceManagement(),
new PagerDuty(),
+1
View File
@@ -324,6 +324,7 @@ export default {
Feishu: "Feishu (飞书)",
FlashDuty: "FlashDuty (快猫星云)",
FreeMobile: "FreeMobile (mobile.free.fr)",
Ooredoo: "Ooredoo (Maldives)",
PushDeer: "PushDeer",
promosms: "PromoSMS",
serwersms: "SerwerSMS.pl",
+64
View File
@@ -0,0 +1,64 @@
<template>
<div class="mb-3">
<label for="ooredoo-username" class="form-label">{{ $t("Username") }}</label>
<input
id="ooredoo-username"
v-model="$parent.notification.ooredooUsername"
type="text"
class="form-control"
required
/>
<div class="form-text">{{ $t("ooredooUsernameDescription") }}</div>
</div>
<div class="mb-3">
<label for="ooredoo-access-key" class="form-label">{{ $t("Access Key") }}</label>
<HiddenInput
id="ooredoo-access-key"
v-model="$parent.notification.ooredooAccessKey"
:required="true"
autocomplete="new-password"
></HiddenInput>
<div class="form-text">{{ $t("ooredooAccessKeyDescription") }}</div>
</div>
<div class="mb-3">
<label for="ooredoo-bearer-token" class="form-label">{{ $t("Bearer Token") }}</label>
<HiddenInput
id="ooredoo-bearer-token"
v-model="$parent.notification.ooredooBearerToken"
:required="true"
autocomplete="new-password"
></HiddenInput>
<div class="form-text">{{ $t("ooredooBearerTokenDescription") }}</div>
</div>
<div class="mb-3">
<label for="ooredoo-to-number" class="form-label">{{ $t("Recipient Number") }}</label>
<input
id="ooredoo-to-number"
v-model="$parent.notification.ooredooToNumber"
type="text"
class="form-control"
required
/>
<div class="form-text">{{ $t("ooredooRecipientDescription") }}</div>
</div>
<div class="mb-3">
<label for="ooredoo-server-url" class="form-label">{{ $t("API URL") }}</label>
<input
id="ooredoo-server-url"
v-model="$parent.notification.ooredooServerUrl"
type="text"
class="form-control"
placeholder="https://o-papi1-lb01.ooredoo.mv/bulk_sms/v2"
/>
<div class="form-text">{{ $t("Leave blank to use the default Ooredoo endpoint.") }}</div>
</div>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
};
</script>
+2
View File
@@ -37,6 +37,7 @@ import Octopush from "./Octopush.vue";
import OneChat from "./OneChat.vue";
import OneBot from "./OneBot.vue";
import Onesender from "./Onesender.vue";
import Ooredoo from "./Ooredoo.vue";
import Opsgenie from "./Opsgenie.vue";
import JiraServiceManagement from "./JiraServiceManagement.vue";
import PagerDuty from "./PagerDuty.vue";
@@ -138,6 +139,7 @@ const NotificationFormList = {
OneChat: OneChat,
OneBot: OneBot,
Onesender: Onesender,
Ooredoo: Ooredoo,
Opsgenie: Opsgenie,
JiraServiceManagement: JiraServiceManagement,
PagerDuty: PagerDuty,
+6
View File
@@ -957,6 +957,12 @@
"smspartnerSenderName": "SMS Sender Name",
"smspartnerSenderNameInfo": "Must be between 3..=11 regular characters",
"Recipient Number": "Recipient Number",
"Access Key": "Access Key",
"ooredooUsernameDescription": "The email address you use to sign in to the Ooredoo bulk SMS dashboard.",
"ooredooAccessKeyDescription": "Found under \"API Access Key\" on your Ooredoo SMS dashboard. Paste it exactly as shown — it is base64-encoded automatically before sending.",
"ooredooBearerTokenDescription": "The token shown after \"Authorization: Bearer\" in the sample cURL command on your Ooredoo SMS dashboard.",
"ooredooRecipientDescription": "Enter one or more numbers separated by a comma or space. Local 7-digit numbers are automatically prefixed with 960.",
"Leave blank to use the default Ooredoo endpoint.": "Leave blank to use the default Ooredoo endpoint.",
"From Name/Number": "From Name/Number",
"Leave blank to use a shared sender number.": "Leave blank to use a shared sender number.",
"Octopush API Version": "Octopush API Version",