diff --git a/api.go b/api.go index 6a0b6cb..ab0bb43 100644 --- a/api.go +++ b/api.go @@ -7,21 +7,17 @@ import ( "strings" ) -// SendBatch sends multiple SMS. -func (c *Client) SendBatch(messages []Message) (*MessageResponse, error) { +// Send an SMS. +func (c *Client) Send(message Message) (*MessageResponse, error) { q := c.buildDefaultQuery() - q.Set("encoding", "UTF8") - u, _ := url.Parse("SmSendPost.asp") + q.Set("CharsetURL", "UTF8") + + u, _ := url.Parse("/api/mtk/SmSend") u.RawQuery = q.Encode() var ini string - for i, message := range messages { - ini += "[" + strconv.Itoa(i) + "]\n" - ini += message.ToINI() - } - ini = strings.TrimSpace(ini) - - resp, err := c.Post(u.String(), "text/plain", strings.NewReader(ini)) + ini = strings.TrimSpace(message.ToINI()) + resp, err := c.Post(u.String(), "application/x-www-form-urlencoded", strings.NewReader(ini)) if err != nil { return nil, err } @@ -30,19 +26,17 @@ func (c *Client) SendBatch(messages []Message) (*MessageResponse, error) { return parseMessageResponse(resp.Body) } -// SendLongMessageBatch sends multiple long message SMS. -func (c *Client) SendLongMessageBatch(messages []Message) (*MessageResponse, error) { +// SendBatch sends multiple SMS. +func (c *Client) SendBatch(messages []Message) (*MessageResponse, error) { q := c.buildDefaultQuery() q.Set("Encoding_PostIn", "UTF8") - u := *c.LongMessageBaseURL - u.Path = "SpLmPost" + u, _ := url.Parse("/api/mtk/SmBulkSend") u.RawQuery = q.Encode() var ini string for _, message := range messages { - ini += message.ID + "$$" - ini += message.ToLongMessage() + ini += message.ToBatchMessage() } ini = strings.TrimSpace(ini) @@ -52,22 +46,12 @@ func (c *Client) SendLongMessageBatch(messages []Message) (*MessageResponse, err } defer resp.Body.Close() - return parseLongMessageResponse(resp.Body) -} - -// Send an SMS. -func (c *Client) Send(message Message) (*MessageResponse, error) { - return c.SendBatch([]Message{message}) -} - -// SendLongMessage sends a long SMS. -func (c *Client) SendLongMessage(message Message) (*MessageResponse, error) { - return c.SendLongMessageBatch([]Message{message}) + return parseMessageResponse(resp.Body) } // QueryAccountPoint retrieves your account balance. func (c *Client) QueryAccountPoint() (int, error) { - u, _ := url.Parse("SmQueryGet.asp") + u, _ := url.Parse("/api/mtk/SmQuery") u.RawQuery = c.buildDefaultQuery().Encode() resp, err := c.Get(u.String()) @@ -88,7 +72,7 @@ func (c *Client) QueryMessageStatus(messageIds []string) (*MessageStatusResponse q := c.buildDefaultQuery() q.Set("msgid", strings.Join(messageIds, ",")) - u, _ := url.Parse("SmQueryGet.asp") + u, _ := url.Parse("/api/mtk/SmQuery") u.RawQuery = q.Encode() resp, err := c.Get(u.String()) @@ -105,7 +89,7 @@ func (c *Client) CancelMessageStatus(messageIds []string) (*MessageStatusRespons q := c.buildDefaultQuery() q.Set("msgid", strings.Join(messageIds, ",")) - u, _ := url.Parse("SmCancel.asp") + u, _ := url.Parse("/api/mtk/SmCancel") u.RawQuery = q.Encode() resp, err := c.Get(u.String()) diff --git a/api_test.go b/api_test.go index 61baf6e..447a4f6 100644 --- a/api_test.go +++ b/api_test.go @@ -11,7 +11,7 @@ func TestClient_SendBatch(t *testing.T) { client, mux, teardown := setup() defer teardown() - mux.HandleFunc("/SmSendPost.asp", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/api/mtk/SmBulkSend", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testINI(t, r, `[0] dstaddr=0987654321 @@ -30,12 +30,14 @@ AccountPoint=98`) messages := []Message{ { - Dstaddr: "0987654321", - Smbody: "Test 1", + ClientID: "0", + Dstaddr: "0987654321", + Smbody: "Test 1", }, { - Dstaddr: "0987654322", - Smbody: "Test 2", + ClientID: "1", + Dstaddr: "0987654322", + Smbody: "Test 2", }, } @@ -61,69 +63,11 @@ AccountPoint=98`) } } -func TestClient_SendLongMessageBatch(t *testing.T) { - client, mux, teardown := setup() - defer teardown() - - mux.HandleFunc("/SpLmPost", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") - testINI(t, r, `0aab$$0987654321$$20170101010000$$20170101012300$$Bob$$https://example.com/callback$$Test1 -1aab$$0987654321$$$$$$Bob$$$$Test2`) - _, _ = fmt.Fprint(w, `[0aab] -msgid=#1010079522 -statuscode=1 -[1aab] -msgid=#1010079523 -statuscode=4 -AccountPoint=98`) - }) - - messages := []Message{ - { - ID: "0aab", - Destname: "Bob", - Dlvtime: "20170101010000", - Vldtime: "20170101012300", - Dstaddr: "0987654321", - Smbody: "Test1", - Response: "https://example.com/callback", - }, - { - ID: "1aab", - Destname: "Bob", - Dstaddr: "0987654321", - Smbody: "Test2", - }, - } - - resp, err := client.SendLongMessageBatch(messages) - - if err != nil { - t.Errorf("SendLongMessageBatch returned unexpected error: %v", err) - } - - want := []*MessageResult{ - { - Msgid: "#1010079522", - Statuscode: "1", - Statusstring: StatusCode("1"), - }, - { - Msgid: "#1010079523", - Statuscode: "4", - Statusstring: StatusCode("4"), - }, - } - if !reflect.DeepEqual(resp.Results, want) { - t.Errorf("SendLongMessageBatch returned %+v, want %+v", resp.Results, want) - } -} - func TestClient_Send(t *testing.T) { client, mux, teardown := setup() defer teardown() - mux.HandleFunc("/SmSendPost.asp", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/api/mtk/SmSend", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testINI(t, r, `[0] dstaddr=0987654321 @@ -156,49 +100,11 @@ AccountPoint=99`) } } -func TestClient_SendLongMessage(t *testing.T) { - client, mux, teardown := setup() - defer teardown() - - mux.HandleFunc("/SpLmPost", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "POST") - testINI(t, r, `0aab$$0987654321$$$$$$John$$https://example.com/callback$$Test1`) - _, _ = fmt.Fprint(w, `[0aab] -msgid=#1010079522 -statuscode=1 -AccountPoint=99`) - }) - - resp, err := client.SendLongMessage( - Message{ - ID: "0aab", - Destname: "John", - Dstaddr: "0987654321", - Smbody: "Test1", - Response: "https://example.com/callback", - }, - ) - if err != nil { - t.Errorf("SendLongMessage returned unexpected error: %v", err) - } - - want := []*MessageResult{ - { - Msgid: "#1010079522", - Statuscode: "1", - Statusstring: StatusCode("1"), - }, - } - if !reflect.DeepEqual(resp.Results, want) { - t.Errorf("SendLongMessage returned %+v, want %+v", resp.Results, want) - } -} - func TestClient_QueryAccountPoint(t *testing.T) { client, mux, teardown := setup() defer teardown() - mux.HandleFunc("/SmQueryGet.asp", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/api/mtk/SmQuery", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") _, _ = fmt.Fprint(w, `AccountPoint=100`) }) @@ -216,7 +122,7 @@ func TestClient_QueryMessageStatus(t *testing.T) { client, mux, teardown := setup() defer teardown() - mux.HandleFunc("/SmQueryGet.asp", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/api/mtk/SmQuery", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") _, _ = fmt.Fprint(w, `1010079522 1 20170101010010 1010079523 4 20170101010011`) @@ -254,7 +160,7 @@ func TestClient_CancelMessage(t *testing.T) { client, mux, teardown := setup() defer teardown() - mux.HandleFunc("/SmCancel.asp", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/api/mtk/SmCancel", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") _, _ = fmt.Fprint(w, `1010079522=8 1010079523=9`) diff --git a/message.go b/message.go index 3e1a2aa..53762e8 100644 --- a/message.go +++ b/message.go @@ -31,6 +31,7 @@ const ( StatusAccountExpired = StatusCode("f") StatusAccountDisabled = StatusCode("h") StatusInvalidConnectionAddress = StatusCode("k") + StatusReachConnectUserLimit = StatusCode("l") StatusChangePasswordRequired = StatusCode("m") StatusPasswordExpired = StatusCode("n") StatusPermissionDenied = StatusCode("p") @@ -39,6 +40,10 @@ const ( StatusSMSExpired = StatusCode("t") StatusSMSBodyEmpty = StatusCode("u") StatusInvalidPhoneNumber = StatusCode("v") + StatusQueryRecordExceededLimit = StatusCode("w") + StatusSMSSizeTooLarge = StatusCode("x") + StatusParameterError = StatusCode("y") + StatusNoRecord = StatusCode("z") StatusReservationForDelivery = StatusCode("0") StatusCarrierAccepted = StatusCode("1") StatusCarrierAccepted2 = StatusCode("2") @@ -61,6 +66,7 @@ var statusCodeMap = map[StatusCode]string{ StatusAccountExpired: "帳號已過期", StatusAccountDisabled: "帳號已被停用", StatusInvalidConnectionAddress: "無效的連線位址", + StatusReachConnectUserLimit: "帳號已達到同時連線數上限", StatusChangePasswordRequired: "必須變更密碼,在變更密碼前,無法使用簡訊發送服務", StatusPasswordExpired: "密碼已逾期,在變更密碼前,將無法使用簡訊發送服務", StatusPermissionDenied: "沒有權限使用外部Http程式", @@ -69,6 +75,10 @@ var statusCodeMap = map[StatusCode]string{ StatusSMSExpired: "簡訊已過期", StatusSMSBodyEmpty: "簡訊內容不得為空白", StatusInvalidPhoneNumber: "無效的手機號碼", + StatusQueryRecordExceededLimit: "查詢筆數超過上限", + StatusSMSSizeTooLarge: "發送檔案過大,無法發送簡訊", + StatusParameterError: "參數錯誤", + StatusNoRecord: "查無資料", StatusReservationForDelivery: "預約傳送中", StatusCarrierAccepted: "已送達業者", StatusCarrierAccepted2: "已送達業者", @@ -83,14 +93,14 @@ var statusCodeMap = map[StatusCode]string{ // Message represents an SMS object. type Message struct { - ID string `json:"id"` // Default ID of the message Dstaddr string `json:"dstaddr"` // Destination phone number Destname string `json:"destname"` // Destination receiver name - Smbody string `json:"smbody"` // The text of the message you want to send Dlvtime string `json:"dlvtime"` // Optional, Delivery time Vldtime string `json:"vldtime"` // Optional + Smbody string `json:"smbody"` // The text of the message you want to send Response string `json:"response"` // Optional, Callback URL to receive the delivery receipt of the message - ClientID string `json:"clientid"` // Optional, an unique identifier from client to identify SMS message + ClientID string `json:"clientid"` // Optional (required when bulk send), an unique identifier from client to identify SMS message + // ObjectID string `json:"objectID"` // Optional } // ToINI returns the INI format string from the message fields. @@ -98,26 +108,32 @@ func (m Message) ToINI() string { smbody := strings.Replace(m.Smbody, "\n", string(byte(6)), -1) var ini string - ini += "dstaddr=" + m.Dstaddr + "\n" - ini += "smbody=" + smbody + "\n" + ini += "dstaddr=" + m.Dstaddr + "&" + ini += "smbody=" + smbody + "&" + if m.Destname != "" { + ini += "destname=" + m.Destname + "&" + } if m.Dlvtime != "" { - ini += "dlvtime=" + m.Dlvtime + "\n" + ini += "dlvtime=" + m.Dlvtime + "&" } if m.Vldtime != "" { - ini += "vldtime=" + m.Vldtime + "\n" + ini += "vldtime=" + m.Vldtime + "&" } if m.Response != "" { - ini += "response=" + m.Response + "\n" + ini += "response=" + m.Response + "&" } if m.ClientID != "" { - ini += "ClientID=" + m.ClientID + "\n" + ini += "clientid=" + m.ClientID + "&" } return ini } -// ToLongMessage returns the format string for Long SMS. -func (m Message) ToLongMessage() string { +// ToBatchMessage returns the format string for multiple SMS. +func (m Message) ToBatchMessage() string { + smbody := strings.Replace(m.Smbody, "\n", string(byte(6)), -1) + var ini string + ini += m.ClientID + "$$" // The document says this field is REQUIRED ini += m.Dstaddr + "$$" if m.Dlvtime != "" { ini += m.Dlvtime @@ -135,15 +151,17 @@ func (m Message) ToLongMessage() string { ini += m.Response } ini += "$$" - ini += m.Smbody + "\n" + ini += smbody + "\n" return ini } // MessageResult represents result of send SMS. type MessageResult struct { + ClientID string `json:"clientid"` Msgid string `json:"msgid"` Statuscode string `json:"statuscode"` Statusstring StatusCode `json:"statusstring"` + Duplicate string `json:"Duplicate"` } // MessageResponse represents response of send SMS. @@ -166,14 +184,19 @@ func parseMessageResponseByPattern(pattern string, body io.Reader) (*MessageResp if matched, _ := regexp.MatchString(pattern, text); matched { result = new(MessageResult) response.Results = append(response.Results, result) + // ClientID + re := regexp.MustCompile(pattern) + result.ClientID = re.FindStringSubmatch(text)[1] } else { strs := strings.Split(text, "=") switch strs[0] { case "msgid": result.Msgid = strs[1] case "statuscode": - result.Statusstring = StatusCode(strs[1]) result.Statuscode = strs[1] + result.Statusstring = StatusCode(strs[1]) + case "Duplicate": + result.Duplicate = strs[1] case "AccountPoint": response.AccountPoint, _ = strconv.Atoi(strs[1]) } @@ -186,11 +209,7 @@ func parseMessageResponseByPattern(pattern string, body io.Reader) (*MessageResp } func parseMessageResponse(body io.Reader) (*MessageResponse, error) { - return parseMessageResponseByPattern(`^\[\d+\]$`, body) -} - -func parseLongMessageResponse(body io.Reader) (*MessageResponse, error) { - return parseMessageResponseByPattern(`^\[[a-zA-z0-9]+\]$`, body) + return parseMessageResponseByPattern(`^\[(.+?)\]$`, body) } // MessageStatus represents status of message. @@ -218,8 +237,8 @@ func parseMessageStatusResponse(body io.Reader) (*MessageStatusResponse, error) response.Statuses = append(response.Statuses, &MessageStatus{ MessageResult: MessageResult{ Msgid: strs[0], - Statusstring: StatusCode(strs[1]), Statuscode: strs[1], + Statusstring: StatusCode(strs[1]), }, StatusTime: strs[2], }) @@ -243,8 +262,8 @@ func parseCancelMessageStatusResponse(body io.Reader) (*MessageStatusResponse, e response.Statuses = append(response.Statuses, &MessageStatus{ MessageResult: MessageResult{ Msgid: strs[0], - Statusstring: StatusCode(strs[1]), Statuscode: strs[1], + Statusstring: StatusCode(strs[1]), }, }) } diff --git a/message_test.go b/message_test.go index 505e3c9..f8b7d31 100644 --- a/message_test.go +++ b/message_test.go @@ -10,13 +10,14 @@ import ( func TestMessage_ToINI(t *testing.T) { message1 := Message{ Dstaddr: "0987654321", - Smbody: "Test", + Destname: "Human", Dlvtime: "20170101010000", Vldtime: "20170101012300", + Smbody: "Test", Response: "https://example.com/callback", ClientID: "R123lB29988uDydrjbABCD", } - want1 := "dstaddr=0987654321\nsmbody=Test\ndlvtime=20170101010000\nvldtime=20170101012300\nresponse=https://example.com/callback\nClientID=R123lB29988uDydrjbABCD\n" + want1 := "dstaddr=0987654321&destname=Human&dlvtime=20170101010000&vldtime=20170101012300&smbody=Test&response=https://example.com/callback&ClientID=R123lB29988uDydrjbABCD&&" if got := message1.ToINI(); got != want1 { t.Errorf("Message INI is %v, want %v", got, want1) } @@ -24,31 +25,33 @@ func TestMessage_ToINI(t *testing.T) { Dstaddr: "0987654321", Smbody: "Test", } - want2 := "dstaddr=0987654321\nsmbody=Test\n" + want2 := "dstaddr=0987654321&smbody=Test&" if got := message2.ToINI(); got != want2 { t.Errorf("Message INI is %v, want %v", got, want1) } } -func TestMessage_ToLongMessage(t *testing.T) { +func TestMessage_ToBatchMessage(t *testing.T) { message1 := Message{ Dstaddr: "0987654321", Destname: "Bob", - Smbody: "Test", Dlvtime: "20170101010000", Vldtime: "20170101012300", + Smbody: "Test", Response: "https://example.com/callback", + ClientID: "21385958-34e8-4d1b-ba6a-c5f0a04c2bea", } - want1 := "0987654321$$20170101010000$$20170101012300$$Bob$$https://example.com/callback$$Test\n" - if got := message1.ToLongMessage(); got != want1 { + want1 := "21385958-34e8-4d1b-ba6a-c5f0a04c2bea$$0987654321$$20170101010000$$20170101012300$$Bob$$https://example.com/callback$$Test\n" + if got := message1.ToBatchMessage(); got != want1 { t.Errorf("Message LM is %v, want %v", got, want1) } message2 := Message{ - Dstaddr: "0987654321", - Smbody: "Test", + Dstaddr: "0987654321", + Smbody: "Test", + ClientID: "812df2f1-4e90-4b68-bdd5-b6dc909c7619", } - want2 := "0987654321$$$$$$$$$$Test\n" - if got := message2.ToLongMessage(); got != want2 { + want2 := "812df2f1-4e90-4b68-bdd5-b6dc909c7619$$0987654321$$$$$$$$$$Test\n" + if got := message2.ToBatchMessage(); got != want2 { t.Errorf("Message LM is %v, want %v", got, want1) } } @@ -89,42 +92,6 @@ AccountPoint=98`) } } -func Test_parseLongMessageResponse(t *testing.T) { - body := strings.NewReader(`[2ks8k828j5] -msgid=#1010079522 -statuscode=1 -[19ke8ks83] -msgid=#1010079523 -statuscode=4 -AccountPoint=98`) - resp, err := parseLongMessageResponse(body) - if err != nil { - t.Errorf("parseMessageResponse returned unexpected error: %v", err) - } - if len(resp.Results) != 2 { - t.Errorf("MessageResponse.Result len is %d, want %d", len(resp.Results), 2) - } - if resp.AccountPoint != 98 { - t.Errorf("MessageResponse.AccountPoint is %d, want %d", resp.AccountPoint, 98) - } - - want := []*MessageResult{ - { - Msgid: "#1010079522", - Statuscode: "1", - Statusstring: StatusCode("1"), - }, - { - Msgid: "#1010079523", - Statuscode: "4", - Statusstring: StatusCode("4"), - }, - } - if !reflect.DeepEqual(resp.Results, want) { - t.Errorf("MessageResult returned %+v, want %+v", resp.Results, want) - } -} - func Test_parseMessageStatusResponse(t *testing.T) { body := strings.NewReader(`1010079522 1 20170101010010 1010079523 4 20170101010011`) diff --git a/mitake.go b/mitake.go index 45ec629..d64d008 100644 --- a/mitake.go +++ b/mitake.go @@ -10,10 +10,9 @@ import ( ) const ( - libraryVersion = "0.0.1" - defaultUserAgent = "go-mitake/" + libraryVersion - defaultBaseURL = "https://smexpress.mitake.com.tw:9601/" - defaultLongMessageBaseURL = "https://smexpress.mitake.com.tw:7102/" + libraryVersion = "2.08.1" + defaultUserAgent = "go-mitake/" + libraryVersion + defaultBaseURL = "https://smsapi.mitake.com.tw:443/" ) // NewClient returns a new Mitake API client. The username and password are required @@ -27,15 +26,13 @@ func NewClient(username, password string, httpClient *http.Client) *Client { } baseURL, _ := url.Parse(defaultBaseURL) - longMessageBaseURL, _ := url.Parse(defaultLongMessageBaseURL) return &Client{ - client: httpClient, - username: username, - password: password, - UserAgent: defaultUserAgent, - BaseURL: baseURL, - LongMessageBaseURL: longMessageBaseURL, + client: httpClient, + username: username, + password: password, + UserAgent: defaultUserAgent, + BaseURL: baseURL, } } @@ -45,9 +42,8 @@ type Client struct { username string password string - BaseURL *url.URL - LongMessageBaseURL *url.URL - UserAgent string + BaseURL *url.URL + UserAgent string } // checkErrorResponse checks the API response for errors. diff --git a/mitake_test.go b/mitake_test.go index f816846..52ad77e 100644 --- a/mitake_test.go +++ b/mitake_test.go @@ -24,7 +24,6 @@ func setup() (client *Client, mux *http.ServeMux, teardown func()) { // client is the mitake client being tested. client = NewClient("username", "password", nil) client.BaseURL = baseURL - client.LongMessageBaseURL = baseURL return client, mux, server.Close }