How to create notifications modules

From ISPWiki
Jump to: navigation, search

General information

BILLmanager enables you to add notification modules and notification gateways in order to add more functionality to your billing panel.

By default, BILLmanager supports three types of notifications:

  • SMS notifications - messages are sent to phone numbers that users enter in their profile.
    • Notifications can be only sent
    • Gateways are used
  • Email notifications - notifications are sent to users' email (a number of notifications can be sent only to email, as they use specific functions).
    • Notifications can be sent and received
    • Gateways are used for sending and receiving notifications
  • Notifications in the Notifications module. They can be generated based on email to be sent
    • Notifications can be only sent
    • Gateways are not supported

All files of modules and gateways are called with system calls, or in background. They can be written in any programming language, which supports input/output strings.

Notifications principles

The procedure includes the following steps:

  • The system checks what type of notification is activated for a client
  • An XML notification of a required type is generated
  • The XML, containing a notification template, notification XML, and provider and user's information, are sent

The following operations are performed in the module:

  • the system defines a notification type based on its structure.
    • For XSLT templates, the template is written to the XML notification
    • For EJS templates, XML is converted into JSON and is sent to the EJS template engine together with template
  • In the resulting text, macros can be changed into required values, if needed
  • If notifications gateways are not supported, notifications are sent by means of the module
  • If gateways are supported, a gateway is selected based on provider id and notifications module type
  • Gateway parameters, contact data, message text, and its heading are sent to the gateway module.

Module architecture

Every notification or gateway module should be able to process commands described below. If a command is not supported, or if a module outputs data in unsupported format, BILLmanager won't be integrated with that module.

Notification module architecture

The module is installed into /usr/local/mgr5/notify/. It should be able to process the following commands:

  • --command process - process a queue of notifications to be sent. The notifications queue is kept in the notifytask table and has the following structure:
    • id - notification id. It is generated automatically.
    • modulename - name of the notifications module type. Standard values ntemail, ntsms, ntinternal
    • filename - name of the file with notification's data
    • priority - sending priority. We recommend selecting a few notifications in descending order of priority. In case of mass mailing when you sent a large number of notifications, they will be delivered in time.
    • error_count - number of failed attempts to send notifications
    • forcedonothing - enables to send a notification regardless the billmgr.DoNothing file, which is created during data transfer from another billing panel.
    • err_info - error message that is displayed, if the notification cannot be sent
    • createdate - date when the notification was added into queue
  • --command getmessage --gate gate_id, where gate_id - is the gateway id. The all value can be sent as the --gate parameter, in this case notifications should be processed by all gateways of the notifications type.
  • --command features - call parameters of the notification module. The module should send XML description of supported functions to STDOUT. XML format:
<?xml version="1.0" encoding="UTF-8"?>
<doc>
  <features>
    <feature name="html"/> 
    <feature name="sms"/> 
    <feature name="call"/> 
  </features>
  <contact_type>contact type</contact_type> 
</doc>

Developers can define a queue processing procedure, and operations against received messages. For example, you can use the following scenarios:

  • Continuous operation of the module with periodic checks of new messages in the queue
  • Add incoming messages to clients' tickets
  • Manage services via incoming messages
  • Request and output information by control commands

If you write the module with header files, the following methods should be implemented.

  • virtual mgr_xml::Xml Features() const = 0; - returns XML description of supported features. Data will be automatically sent to STDOUT
  • virtual bool UserNotify(const string& filename) const = 0; - send a notification described in the file with the name, which was passed in the parameter
  • virtual void GetMessage(string gate_id = 0) const = 0; - process incoming messages for the gateway with code gate_id

The following method can be passed as well, if needed

  • virtual int ProcessQueue() const; - process notifications queue. UserNotify calls a class, and can be empty, if the procedure is specified in ProcessQueue

Architecture of gateway module

The module is installed into /usr/local/mgr5/gate/. It should be able to process the following commands:

  • --command features - call parameters of the gateway module. The module should send XML description of supported functionality to STDOUT. Format of an XML document looks like that:
<?xml version="1.0" encoding="UTF-8"?>
<doc>
  <features>
    <feature name="outgoing"/> 
    <feature name="ingoing"/> 
    <feature name="formtune"/> 
    <feature name="check_connection"/> 
  </features>
  <notify_module>notifications module type</notify_module> 
</doc>
  • --command formtune - modify a gateway parameters' edit form. XML of the gateway edit form is sent to STDIN, and a modified XML of the edit form description is returned to STDOUT.
  • --command check_connection - check connection to the gateway with specified parameters. XML description of gateway parameters form with data that was entered into that form, is sent to STDIN. XML description of the configuration form should be sent to STDOUT (XML can be modified if needed).
  • --command outgoing и --command ingoing - they are not called by BILLmanager directly, except for SMS - in this case --command outgoing must be supported in a certain format. In all other cases you can describe any commands that are called by notifications modules. You can find description of how standard module works with those parameters:
  • --command outgoing - send notifications. The following XML will be sent to module input:
<?xml version="1.0" encoding="UTF-8"?>
<doc>
  <gateway> - gateway parameters 
    <param>value</param>
    <param>value</param>
    ...
    <param>value</param>
    <xmlparams>Gateway connection parameters in  XML</xmlparams>
  </gateway>
  <message>notification text</message>
  <user> - user parameters
    <param>value</param>
    <param>value</param>
    ...
    <param>value</param>
  </user>
  <project> - provider parameters 
    <param>value</param>
    <param>value</param>
    ...
    <param>value</param>
  </project>
</doc>

The module should sent an empty XML or XML description of an error to STDOUT.

  • --command ingoing (is used to receive mail) - XML with gateway parameters as described above will be passed to STDIN. XML with a list of received messages will be passed to STDOUT
<?xml version="1.0" encoding="UTF-8"?>
<doc>
  <messages>
    <message>source text/message>
    ...
    <message>source text</message>
  </messages>
</doc>

Implementation of the module with BILLmanager header files assumes that the following methods are supported:

virtual mgr_xml::Xml Features() const = 0; - XML description of supported features

virtual mgr_xml::Xml Ingoing(mgr_xml::Xml& input) const = 0; - XML with gateway parameters is sent to STDIN (parameters can be obtained with the GateParam method), a list of messages in the described format is sent to STDOUT.

virtual void Outgoing(mgr_xml::Xml& input) const = 0; - XML with gateways parameters and messages to be sent are passed to STDIN. (parameters can be obtained with the GateParam method)

Examples of modules

C++ (using BILLmanager libraries in the developer package)

Starting from version 5.58 you can use BILLmanager header files to create custom processing modules. Beside that simplified example, you can look into examples in BILLmanager developer package - billmanager-[BILLmanager release version]-devel, e.g.:

 yum install billmanager-standard-devel

Then you can find examples in the directory:

/usr/local/mgr5/src/examples


C++ (using BILLmanager libraries)

Follow the link below to find a module for notifications and XMPP gateway

https://github.com/ISPsystemLLC/jabber

The example is written on C++ using COREmanager and BILLmanager header files, as well as libraries Gloox. The example consists of:

  • The ntjabber notifications module - the main file ntjabber.cpp. It is responsible for notifications and enables to add notifications templates, as well as create mass mailing of a required type.
  • The gateway module for connecting to the Jabber server gwjabber - the main file gwjabber.cpp. It is responsible for sending messages to Jabber contact of a certain user, and handling incoming messages.
  • XML files that describe required messages and server connection parameters:
    • billmgr_mod_ntjabber.xml - add a Jabber contact field on the user edit form (values are displayed ans saved automatically according to mechanism described here), add the notifications type
    • billmgr_mod_gwjabber.xml - describes jabber-server connection parameters, and name of the connection module
  • Description of the jabber database field - describes an additional field for the user table in BILLmanager database
  • Logo of the billmanager-plugin-gwjabber.png gateway - enables to display the XMPP logo on a gateway type selection form.
  • File containing the Makefile build description

Other programming languages

Module XML

XML description of the module is saved into the /usr/local/mgr5/etc/xml/billmgr_mod_ntxxx.xml file for notifications module, and into the /usr/local/mgr5/etc/xml/billmgr_mod_gwxxx.xml file for gateway modules, where xxx is a unique name of the module. In the following example you can find an XML description of a gateway module. XML file of the notifications module is described in the "Example in C++" section.

The file has the following format (example of integration with ePochta SMS)

<?xml version="1.0" encoding="UTF-8"?>
<mgrdata>
  <plugin name="gwepochta">           <!-- plug-in description  in BILLmanager -->
    <group>gateway</group>            <!-- email gateways associated with that plug-in-->
    <author>BILLmanager team</author> <!-- module author -->
  </plugin>
  <metadata name="gateway.gwepochta"> <!-- description of module settings-->
    <form>
      <field name="login">
        <input type="text" name="login" required="yes" identifier="yes"/>
      </field>
      <field name="password">
        <input type="password" name="password" required="yes"/>
      </field>
      <field name="sender">
        <input type="text" name="sender" required="yes"/>
      </field>
    </form>
  </metadata>
  <lang name="ru">
    <messages name="plugin">         <!-- message for plug-in description -->
      <msg name="desc_short_gwepochta">ePochta SMS</msg>
      <msg name="desc_full_gwepochta">ePochta SMS</msg>
      <msg name="price_gwepochta">Free</msg>
    </messages>
    <messages name="gateway.gwepochta"> <!-- message for module settings form -->
      <msg name="login">Login</msg>
      <msg name="password">Password</msg>
      <msg name="sender">Sender</msg>
      <msg name="hint_login">Login to ePochta SMS client area</msg>
      <msg name="hint_password">Password to Client area</msg>
      <msg name="hint_sender">Signature of the message sender</msg>
    </messages>
    <messages name="gateway_include"> <!-- module name that will be displayed in BILLmanager -->
      <msg name="module_gwepochta">Server ePochta SMS</msg>
      <msg name="gwepochta">ePochta SMS</msg>
      <msg name="desc_gwepochta">ePochta SMS</msg>
    </messages>
  </lang>
  <lang name="en"> <!-- English localization of messages -->
    <messages name="plugin">
      <msg name="desc_short_gwepochta">ePochta SMS</msg>
      <msg name="desc_full_gwepochta">Server ePochta SMS</msg>
      <msg name="price_gwepochta">Free</msg>
    </messages>
  </lang>
</mgrdata>

Go

package main

import "bytes"
import "log"
import "encoding/xml"
import "flag"
import "fmt"
import "os"
import "io/ioutil"
import "net/http"

func request(operation, username, password, phone, message, sender string) (string, string) {
  type SMS struct {
    XMLName    xml.Name  `xml:"SMS"`
    Operation  string    `xml:"operations>operation"`
    Username  string    `xml:"authentification>username"`
    Password  string    `xml:"authentification>password"`
    Message    string    `xml:"message>text"`
    Sender    string    `xml:"message>sender"`
    Number    string    `xml:"numbers>number"`
  }
  
  v := &SMS{
      Operation:   operation,
      Username:  username,
      Password:  password,
      Message:  message,
      Sender:    sender,
      Number:    phone,
  }

  output, err := xml.MarshalIndent(v, "  ", "    ")
  log.Print("REQUEST: " + string(output))
    
  if err != nil {
    return "", ""
  }
  
  resp, err := http.Post("http://api.myatompark.com/members/sms/xml.php", "image/jpeg", bytes.NewBuffer(output))
  
  if err != nil {
    return "", ""
  }
  
  defer resp.Body.Close()
  body, err := ioutil.ReadAll(resp.Body)
  
  log.Print("RESPONSE: " + string(body))
  
  type Response struct {
    XMLName    xml.Name  `xml:"RESPONSE"`
    Status    int      `xml:"status"`
    }
  
  r := Response{Status: 1}
  resperr := xml.Unmarshal(body, &r)
  if resperr != nil {
    log.Printf("error: %v", resperr)
    return "", ""
  }
  
  if r.Status != 0 {
    return string(body), "error"
  }
  
  return string(body), ""
}

func main() {
  f, _ := os.OpenFile("var/gwepochta.log", os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666)
  defer f.Close()
  
  log.SetOutput(f)
    
  command_ptr := flag.String("command", "features", "gateway command")

  flag.Parse()

  if *command_ptr == "features" {
    type Feature struct {
      XMLName  xml.Name  `xml:"feature"`
      Name  string    `xml:"name,attr"`
    }

    type Features struct {
      XMLName    xml.Name   `xml:"doc"`
      Features   []Feature    `xml:"features>feature"`
      Module    string    `xml:"notify_module"`
    }

    v := &Features{
        Module: "ntsms",
        Features: []Feature {
                {Name: "formtune"},
                {Name: "check_connection"},
                {Name: "outgoing"},
        },
      }

    output, err := xml.MarshalIndent(v, "  ", "    ")
    if err != nil {
      fmt.Println("error: %v\n", err)
    }

    os.Stdout.Write(output)
  } else if *command_ptr == "formtune" {
    bytes, _ := ioutil.ReadAll(os.Stdin)
    os.Stdout.Write(bytes)
  } else if *command_ptr == "check_connection" {
    type Doc struct {
      XMLName   xml.Name   `xml:"doc"`
      XMLparams    string    `xml:"xmlparams"`
      Login    string    `xml:"login"`
      Password  string    `xml:"password"`
    }
    
    bytes, _ := ioutil.ReadAll(os.Stdin)
    
    v := Doc{XMLparams: "none", Login: "none", Password: "none"}
    err := xml.Unmarshal(bytes, &v)
    if err != nil {
      log.Printf("error: %v", err)
      return
    }
    
    paramerr := xml.Unmarshal([]byte(v.XMLparams), &v)
    if paramerr != nil {
      log.Printf("error: %v", paramerr)
      return
    }

    _, error := request("BALANCE", v.Login, v.Password, "", "", "")
    
    if error != "" {
      type Error struct {
        Type  string  `xml:"type,attr"`
      }
      
      type Doc struct {
        XMLName   xml.Name   `xml:"doc"`
        ErrorType    Error    `xml:"error"`
      }
      
      t := &Error {
        Type: error,
      }
      
      v := &Doc{
        ErrorType:   *t,
      }

      output, _ := xml.MarshalIndent(v, "  ", "    ")
      os.Stdout.Write(output)
      return
    }
    os.Stdout.Write(bytes)
  } else if *command_ptr == "outgoing" {
    bytes, _ := ioutil.ReadAll(os.Stdin)
    log.Print(string(bytes))
    
    type Doc struct {
      XMLName   xml.Name   `xml:"doc"`
      XMLparams    string    `xml:"gateway>xmlparams"`
      Login    string    `xml:"login"`
      Password  string    `xml:"password"`
      Sender    string    `xml:"sender"`
      Message    string    `xml:"message"`
      Phone    string    `xml:"user>phone"`
    }
    
    v := Doc{XMLparams: "none", Login: "none", Password: "none", Message: "none", Phone: "none"}
    err := xml.Unmarshal(bytes, &v)
    
    log.Print(v.XMLparams)
    
    if err != nil {
      log.Printf("error: %v", err)
      return
    }
    
    xml.Unmarshal([]byte(v.XMLparams), &v)
    
    request("SEND", v.Login, v.Password, v.Phone, v.Message, v.Sender)
  }
}