linxGnu / gosmpp

Smpp (3.4) Client Library for Go
Apache License 2.0
152 stars 59 forks source link

Delivery Receipt Parameters #124

Closed tahseenjamal closed 9 months ago

tahseenjamal commented 9 months ago

SMPP has dlr receipts message like

id:123A456B sub:1 dlvrd:1 submit date:1702281424 done date:1702281424 stat:DELIVRD err:0 text: hello how are you there

How to do get these parameters ?

ml-markhay commented 9 months ago

Message ID, status and error are often contained in a DLR as TLVs and can be extracted using TagReceiptedMessageID, TagMessageStateOption, TagNetworkErrorCode

See https://github.com/linxGnu/gosmpp/blob/master/pdu/TLV.go

tahseenjamal commented 9 months ago

@ml-markhay

While I have extracted using message, err := pd.Message.GetMessage()

Once in the I have pd

switch pd := p.(type) {
    case *pdu.DeliverSM:

}

So what do I do after case statement ?

ml-markhay commented 9 months ago
if pd.EsmClass == 4 {
  log.Println("DLR")

  fmt.Println("Receipted Message ID: ",pd.OptionalParameters[pdu.TagReceiptedMessageID])
  fmt.Println("Message State: ",pd.OptionalParameters[pdu.TagMessageStateOption])
  fmt.Println("Network Error",pd.OptionalParameters[pdu.TagNetworkErrorCode])
}

Runnable example at https://melroselabs.com/tools/smscodebench/

Receipted Message ID:  {30 [51 53 51 52 98 99 101 53 99 100 52 57 97 48 101 57 54 101 49 52 57 50 49 55 51 56 54 57 48 48 100 57 56 54 52 54 0]}
Message State:  {1063 [2]}
Network Error {1059 [3 0 0]}
tahseenjamal commented 9 months ago

I think the approach I used is simpler

// initialise regular expression in the init function 
func init() {
    pattern := `id:(\w+) sub:(\d+) dlvrd:(\d+) submit date:(\d+) done date:(\d+) stat:(\w+) err:(\d+) [Tt]ext:(.+)`
    re = regexp.MustCompile(pattern)

}

func extract(message string) map[string]string {

    matches := re.FindStringSubmatch(message)
    var resultMap = make(map[string]string)
    for i, key := range keys {
        keys := []string{"id", "sub", "dlvrd", "submit_date", "done_date", "stat", "err", "text"}
        resultMap[key] = matches[i+1]
    }
    return resultMap
}

Now that message in case statement

dlrMessage, _ := pd.Message.GetMessage()
dlrMap := extract(dlrMessage)

now using key names, I can check the values

sujit-baniya commented 9 months ago

I'm using following approach


type GenericResponse struct {
    Id         string `csv:"id" json:"id"`
    Sub        string `csv:"sub" json:"sub"`
    Dlvrd      string `csv:"dlvrd" json:"dlvrd"`
    SubmitDate string `csv:"submit date" json:"submit date"`
    DoneDate   string `csv:"done date" json:"done date"`
    Stat       string `csv:"stat" json:"stat"`
    Err        string `csv:"err" json:"err"`
    Text       string `csv:"text" json:"text"`
}

func Unmarshal(msg string) GenericResponse {
    fields := []string{"id", "sub", "dlvrd", "submit date", "done date", "stat", "err", "text", "Text"}
    var response GenericResponse
    for _, field := range fields {
        f := field + ":"
        ln1 := strings.Index(msg, f) + len(f)
        v := ""
        if len(msg) >= ln1 {
            if ln2 := strings.Index(msg[ln1:], " "); ln2 != -1 {
                v = msg[ln1:][:ln2]
            } else {
                v = msg[ln1:]
            }
        }
        switch field {
        case "id":
            response.Id = v
        case "sub":
            response.Sub = v
        case "dlvrd":
            response.Dlvrd = v
        case "submit date":
            response.SubmitDate = v
        case "done date":
            response.DoneDate = v
        case "stat":
            response.Stat = v
        case "err":
            response.Err = v
        case "text", "Text":
            response.Text = v
        }
    }
    return response
}
tahseenjamal commented 9 months ago

@sujit-baniya I would prefer regular expression as it is simpler, neater and other developers can understand and change in future if needed

sujit-baniya commented 9 months ago

@tahseenjamal Is there the possibility of change in fields position in response?

laduchesneau commented 9 months ago

@tahseenjamal Is there the possibility of change in fields position in response?

Yes, the fields can be modified on the SMSC. Some carriers even remove the payload (message/text) from the delivery receipt.

sujit-baniya commented 9 months ago

@laduchesneau In that case

// initialise regular expression in the init function 
func init() {
    pattern := `id:(\w+) sub:(\d+) dlvrd:(\d+) submit date:(\d+) done date:(\d+) stat:(\w+) err:(\d+) text:(.+)`
    re = regexp.MustCompile(pattern)

}

func extract(message string) map[string]string {

    matches := re.FindStringSubmatch(message)
    var resultMap = make(map[string]string)
    keys := []string{"id", "sub", "dlvrd", "submit_date", "done_date", "stat", "err", "text"}
    resultMap[key] = matches[i+1]
    return resultMap
}

How would this solution work when there's some missing fields or extra fields in response?

tahseenjamal commented 9 months ago

@sujit-baniya I had missed the for loop, which I added in my code above.

Again after integrating with more than 11 Telco's SMPPs I haven't come across any Telco that has changed the order of fields or removed one field. Everyone has to follow SMMP v3.4 or v5.0 standard

Max they can do is remove the field value, in which case my code would still work but the value of key would be empty as MNO passed no value in DLR.

If Telco removes few fields altogether, I would rather put the keys array in a properties file, for reading at start of application or even in cache for dynamic read/edit - so that I can change it as per Telco's SMPP DLR.

tahseenjamal commented 9 months ago

In above if you want, you can use instead of just text:(.+) like below

[Tt]ext:(.+)

Using [Tt]ext ensures whether SMPP responds with Text or text in DLR, you can parse both

bluewithanas commented 8 months ago

hey @tahseenjamal I have been looking for ways to parse the message string and thanks to you, i can implement the same now! But, how do i parse the submitSMResp. i have also opened an issue issue submitSMResp