infoalloggi-monorepo/apps/infoalloggi/src/server/controllers/skebby.controller.ts
2025-08-29 16:18:32 +02:00

166 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { GetFlagValueHandler } from "~/server/controllers/flags.controller";
import { db } from "~/server/db";
import { NewMail } from "~/server/services/mailer";
const BASEURL = "https://api.skebby.it/API/v1.0/REST/";
enum MessageType {
HIGH_QUALITY = "GP",
MEDIUM_QUALITY = "TI",
LOW_QUALITY = "SI",
}
interface Auth {
user_key: string;
session_key: string;
}
interface SMSData {
/** The type of SMS */
message_type: MessageType;
/** The body of the message. *Message max length could be 160 chars when using low-quality SMSs. New line char needs to be codified as “\n”. */
message: string;
/** A list of recipents phone numbers. The recipients numbers must be in the international format (+393471234567 or 00393471234567) */
recipient: string[];
/** The Sender name. If the message type allows a custom TPOA and the field is left empty, the users preferred TPOA is used. Must be empty if the message type does not allow a custom TPOA */
sender?: string;
/** The messages will be sent at the given scheduled time
* formats: ddMMyy, yyyyMMdd, ddMMyyHHmm, yyyyMMddHHmmss, yyyy-MM-dd HH:mm, yyyy-MM-dd HH:mm.0 */
scheduled_delivery_time?: string;
/** IANA time zone: Europe/Rome, America/New_York, etc. */
scheduled_delivery_timezone?: string;
/** Specifies a custom order ID
* optional, max 32 characters */
order_id?: string;
/** Returns the number of used sms credits. */
returnCredits?: boolean;
/** Returns the number of remaining SMS credits for the quality used in the sent SMS and for the default user nation */
returnRemaining?: boolean;
/** if true, allows sending to invalid recipients */
allowInvalidRecipients?: boolean;
/** default is "gsm" */
encoding?: "gsm" | "ucs2";
id_landing?: number;
/** optional, max 64 characters */
campaign_name?: string;
/** max number of fragments for the message, default is 7 */
max_fragments?: number;
/** if true, truncates the message to fit the max fragments , default is true */
truncate?: boolean;
/** Period after which the SMS message will be deleted from the SMS center. */
validity_period_min?: number;
richsms_url?: string;
}
export type MinimalSMSData = Omit<SMSData, "message_type" | "sender">;
interface SMSResponse {
result: unknown; // or "OK" | any if you're less strict
[key: string]: unknown; // allow additional unknown keys
}
/**
* Authenticates the user given their username and password.
*/
async function login(username: string, password: string): Promise<Auth> {
const url = `${BASEURL}login?username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`;
const response = await fetch(url);
if (response.status === 401) {
throw new Error("Authentication failed: Invalid username or password");
}
if (response.status === 400) {
throw new Error(`Error in request: ${await response.text()}`);
}
if (!response.ok || response.status !== 200) {
throw new Error(`Login failed: ${response.status} ${response.statusText}`);
}
const text = await response.text();
const [user_key, session_key] = text.split(";");
if (!user_key || !session_key) {
throw new Error("Invalid login response format");
}
return { session_key, user_key };
}
/**
* Sends an SMS message
*/
async function sendSMS(auth: Auth, smsData: SMSData) {
const url = `${BASEURL}sms`;
const response = await fetch(url, {
body: JSON.stringify(smsData),
headers: {
"Content-Type": "application/json",
Session_key: auth.session_key,
user_key: auth.user_key,
},
method: "POST",
});
if (response.status === 400) {
throw new Error(`Invalid input: ${await response.text()}`);
}
if (response.status === 401) {
throw new Error("Authentication failed: Invalid user key or session key");
}
if (response.status === 404) {
throw new Error("Not Found: The User_key was not found");
}
if (response.status !== 201) {
const errorText = await response.text();
throw new Error(`SMS send failed: ${response.status} ${errorText}`);
}
const data = (await response.json()) as SMSResponse;
if (data.result !== "OK") {
throw new Error("SMS send returned non-OK result");
}
}
// Example usage
/*const smsData: SMSData = {
recipient: ["+393407246729"],
message: "Hello world!",
message_type: MessageType.HIGH_QUALITY,
sender: "InfoAlloggi",
};*/
export const SendMessage = async (data: MinimalSMSData) => {
const smsFlag = await GetFlagValueHandler({
db: db,
id: "SMS_OFF",
});
if (smsFlag === "true") {
console.warn(
`SMS sending is disabled to:${data.recipient.join(", ")} Mgs:${data.message}`,
);
return {
message: `SMS sending is disabled: to:${data.recipient.join(", ")} Mgs:${data.message}`,
success: true,
};
}
try {
const auth = await login("arcasms2018", "123qwe456rtyA.");
await sendSMS(auth, {
...data,
message_type: MessageType.HIGH_QUALITY,
sender: "InfoAlloggi",
});
} catch (error) {
console.error("Error:", error);
await NewMail({
mailType: "generic",
props: {
testo: `Errore durante l'invio dell'SMS: to:${data.recipient.join(", ")} Mgs:${data.message}; ${error instanceof Error ? error.message : String(error)}`,
title: "Errore invio SMS",
},
subject: "Errore invio SMS",
to: "web@infoalloggi.it",
});
}
};