const Text = require("./text");
const { Image, Document, Video } = require("./media");
/**
* Template API object
*
* @property {String} name The name of the template
* @property {Language} language The language of the template
* @property {Array<(HeaderComponent|BodyComponent|ButtonComponent)>} [components] The components of the template
* @property {String} _ The type of the object, for internal use only
*/
class Template {
/**
* Create a Template object for the API
*
* @param {String} name Name of the template
* @param {(String|Language)} language The code of the language or locale to use. Accepts both language and language_locale formats (e.g., en and en_US).
* @param {...(HeaderComponent|BodyComponent|ButtonComponent)} [components] Components objects containing the parameters of the message. For text-based templates, the only supported component is BodyComponent.
* @throws {Error} If name is not provided
* @throws {Error} If language is not provided
*/
constructor(name, language, ...components) {
if (!name) throw new Error("Template must have a name");
if (!language) throw new Error("Template must have a language");
this.name = name;
this.language = language instanceof Language ? language : new Language(language);
if (components) this.components = components.map(c => typeof c.build === "function" ? c.build() : c).flat();
this._ = "template";
}
}
/**
* Language API object
*
* @property {String} code The code of the language or locale to use. Accepts both language and language_locale formats (e.g., en and en_US).
* @property {String} policy The language policy
*/
class Language {
/**
* Create a Language component for a Template message
*
* @param {String} code The code of the language or locale to use. Accepts both language and language_locale formats (e.g., en and en_US).
* @param {String} [policy] The language policy the message should follow. The only supported option is 'deterministic'. The variable isn't even read by my code :)
* @throws {Error} If code is not provided
*/
constructor(code, policy) {
if (!code) throw new Error("Language must have a code");
this.policy = "deterministic";
this.code = code;
}
}
/**
* Currency API object
*
* @property {Number} amount_1000 The amount of the currency by 1000
* @property {String} code The currency code
* @property {String} fallback_value The fallback value
* @property {String} _ The type of the object, for internal use only
*/
class Currency {
/**
* Builds a currency object for a Parameter
*
* @param {Number} amount_1000 Amount multiplied by 1000
* @param {String} code Currency code as defined in ISO 4217
* @param {String} fallback_value Default text if localization fails
* @throws {Error} If amount_1000 is not provided
* @throws {Error} If code is not provided
* @throws {Error} If fallback_value is not provided
*/
constructor(amount_1000, code, fallback_value) {
if (!amount_1000 && amount_1000 !== 0) throw new Error("Currency must have an amount_1000");
if (!code) throw new Error("Currency must have a code");
if (!fallback_value) throw new Error("Currency must have a fallback_value");
this.amount_1000 = amount_1000;
this.code = code;
this.fallback_value = fallback_value;
this._ = "currency";
}
}
/**
* DateTime API object
*
* @property {String} fallback_value The fallback value
* @property {String} _ The type of the object, for internal use only
*/
class DateTime {
/**
* Builds a date_time object for a Parameter
*
* @param {String} fallback_value Default text. For Cloud API, we always use the fallback value, and we do not attempt to localize using other optional fields.
* @throws {Error} If fallback_value is not provided
*/
constructor(fallback_value) {
if (!fallback_value) throw new Error("Currency must have a fallback_value");
this.fallback_value = fallback_value;
this._ = "date_time";
}
}
/**
* Components API object
*
* @property {String} type The type of the component
* @property {String} sub_type The subtype of the component
* @property {Array<ButtonParameter>} parameters The ButtonParameters to be used in the build function
* @property {Function} build The function to build the component as a compatible API object
*/
class ButtonComponent {
/**
* Builds a button component for a Template message.
* The index of the buttons is defined by the order in which you add them to the Template parameters.
*
* @param {String} sub_type Type of button to create. Can be either 'url' or 'quick_reply'.
* @param {...String} parameters Parameter for each button. The index of each parameter is defined by the order they are sent to the constructor.
* @throws {Error} If sub_type is not either 'url' or 'quick_reply'
* @throws {Error} If parameters is not provided
* @throws {Error} If parameters has over 3 elements
*/
constructor(sub_type, ...parameters) {
if (!["url", "quick_reply"].includes(sub_type)) throw new Error("ButtonComponent sub_type must be either 'url' or 'quick_reply'");
if (!parameters.length) throw new Error("ButtonComponent must have at least 1 parameter");
if (parameters.length > 3) throw new Error("ButtonComponent can only have up to 3 parameters");
const buttonType = sub_type === "url" ? "text" : "payload";
parameters = parameters.map(e => new ButtonParameter(e, buttonType));
this.type = "button";
this.sub_type = sub_type;
this.parameters = parameters;
}
/**
* Generates the buttons components for a Template message. For internal use only.
*
* @returns {Array<{ type: String, sub_type: String, index: String, parameters: Array<ButtonParameter> }>} An array of API compatible buttons components
*/
build() {
return this.parameters.map((p, i) => {
return { type: this.type, sub_type: this.sub_type, index: i.toString(), parameters: [p] };
});
}
}
/**
* Button Parameter API object
*
* @property {String} type The type of the button
* @property {String} [text] The text of the button
* @property {String} [payload] The payload of the button
*/
class ButtonParameter {
/**
* Builds a button parameter for a ButtonComponent
*
* @param {String} param Developer-provided data that is used to fill in the template.
* @param {String} type The type of the button. Can be either 'text' or 'payload'.
* @throws {Error} If param is not provided
* @throws {Error} If type is not either 'text' or 'payload'
*/
constructor(param, type) {
if (!param) throw new Error("UrlButton must have a param");
if (!["text", "payload"].includes(type)) throw new Error("UrlButton type must be either 'text' or 'payload'");
this.type = type;
this[type] = param;
}
}
/**
* Components API object
*
* @property {String} type The type of the component
* @property {Array<Parameter>} [parameters] The parameters of the component
*/
class HeaderComponent {
/**
* Builds a header component for a Template message
*
* @param {...(Text|Currency|DateTime|Image|Document|Video|Parameter)} [parameters] Parameters of the body component
*/
constructor(...parameters) {
this.type = "header";
if (parameters) this.parameters = parameters.map(e => e instanceof Parameter ? e : new Parameter(e, "header"));
}
}
/**
* Components API object
*
* @property {String} type The type of the component
* @property {Array<Parameter>} [parameters] The parameters of the component
*/
class BodyComponent {
/**
* Builds a body component for a Template message
*
* @param {...(Text|Currency|DateTime|Image|Document|Video|Parameter)} [parameters] Parameters of the body component
*/
constructor(...parameters) {
this.type = "body";
if (parameters) this.parameters = parameters.map(e => e instanceof Parameter ? e : new Parameter(e, "body"));
}
}
/**
* Parameter API object
*
* @property {String} type The type of the parameter
* @property {String} [text] The text of the parameter
* @property {Currency} [currency] The currency of the parameter
* @property {DateTime} [datetime] The datetime of the parameter
* @property {Image} [image] The image of the parameter
* @property {Document} [document] The document of the parameter
* @property {Video} [video] The video of the parameter
*/
class Parameter {
/**
* Builds a parameter object for a HeaderComponent or BodyComponent.
* For Text parameter, the header component character limit is 60, and the body component character limit is 1024.
* For Document parameter, only PDF documents are supported for document-based message templates.
*
* @param {(Text|Currency|DateTime|Image|Document|Video)} parameter The parameter to be used in the template
* @param {String} [whoami] The parent component, used to check if a Text object is too long. Can be either 'header' or 'body'
* @throws {Error} If parameter is not provided
* @throws {Error} If parameter is a Text and the parent component (whoami) is "header" and the text over 60 characters
* @throws {Error} If parameter is a Text and the parent component (whoami) is "body" and the text over 1024 characters
*/
constructor(parameter, whoami) {
if (!parameter) throw new Error("Parameter object must have a parameter parameter :)");
this.type = parameter._;
delete parameter._;
// Text type can go to hell
if (this.type === "text") {
if (whoami === "header" && parameter.body > 60) throw new Error("Header text must be 60 characters or less");
if (whoami === "body" && parameter.body > 1024) throw new Error("Body text must be 1024 characters or less");
this[this.type] = parameter.body;
} else this[this.type] = parameter;
}
}
module.exports = {
Template,
Language,
ButtonComponent,
ButtonParameter,
HeaderComponent,
BodyComponent,
Parameter,
Currency,
DateTime
};