package notificationservice import ( "bytes" "encoding/json" "errors" "fmt" "net/http" "net/url" "strings" "bitbucket.org/nemt/nemt-portal-api/application/entitymapping" "bitbucket.org/nemt/nemt-portal-api/application/viewmodel" "bitbucket.org/nemt/nemt-portal-api/domain" "bitbucket.org/nemt/nemt-portal-api/domain/contract" "bitbucket.org/nemt/nemt-portal-api/domain/service" "bitbucket.org/nemt/nemt-portal-api/infra/config" ) const ( twilioMessageURL = "https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json" twilioNumbersURL = "https://api.twilio.com/2010-04-01/Accounts/%s/AvailablePhoneNumbers/US/Local.json" twiliActualNumbersURL = "https://api.twilio.com/2010-04-01/Accounts/%s/IncomingPhoneNumbers.json" ) // twilioService holds methods to twillio application service type twilioService struct { svc *service.Service mapEntity *entitymapping.Mapper cfg *config.Config urlMessage string urlNumber string urlActualNumber string cache contract.CacheManager } // newTwilioService returns a twilioService instance func newTwilioService(svc *service.Service, mapper *entitymapping.Mapper, cfg *config.Config, cache contract.CacheManager) *twilioService { return &twilioService{ svc: svc, mapEntity: mapper, cfg: cfg, urlMessage: fmt.Sprintf(twilioMessageURL, cfg.Twilio.Account), urlNumber: fmt.Sprintf(twilioNumbersURL, cfg.Twilio.Account), urlActualNumber: fmt.Sprintf(twiliActualNumbersURL, cfg.Twilio.Account), cache: cache, } } func (s twilioService) GetProxyNumber(to string) (viewmodel.ProxyNumber, error) { key := "proxy" + to var number viewmodel.ProxyNumber err := s.cache.GetStruct(key, &number) if err != nil || err == domain.ErrCacheMiss { return number, err } else { return number, nil } } func (s twilioService) GetAvaliableNumbers(to string) (viewmodel.ListNumbers, error) { if to != "" { if strings.Contains(to, "+1") { to = strings.Replace(to, "+1", "", -1) } area := to[0:3] msgData := url.Values{} msgData.Set("PhoneNumber", area) client := &http.Client{} req, _ := http.NewRequest("GET", s.urlActualNumber+"?"+msgData.Encode(), bytes.NewBuffer([]byte{})) req.SetBasicAuth(s.cfg.Twilio.Account, s.cfg.Twilio.Token) resp, err := client.Do(req) if err != nil { fmt.Println("Error Twilio: ", err) } defer resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { var data viewmodel.ListNumbers decoder := json.NewDecoder(resp.Body) err := decoder.Decode(&data) if err != nil { return viewmodel.ListNumbers{}, err } return data, nil } else { return viewmodel.ListNumbers{}, errors.New(resp.Status) } } return viewmodel.ListNumbers{}, nil } func (s twilioService) GetAvaliableNewNumbers(to string) (viewmodel.ListNumbers, error) { if to != "" { if strings.Contains(to, "+1") { to = strings.Replace(to, "+1", "", -1) } area := to[0:3] msgData := url.Values{} msgData.Set("AreaCode", area) client := &http.Client{} req, _ := http.NewRequest("GET", s.urlNumber+"?"+msgData.Encode(), bytes.NewBuffer([]byte{})) req.SetBasicAuth(s.cfg.Twilio.Account, s.cfg.Twilio.Token) resp, err := client.Do(req) if err != nil { fmt.Println("Error Twilio: ", err) } defer resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { var data viewmodel.ListNumbers decoder := json.NewDecoder(resp.Body) err := decoder.Decode(&data) if err != nil { return viewmodel.ListNumbers{}, err } return data, nil } else { return viewmodel.ListNumbers{}, errors.New(resp.Status) } } return viewmodel.ListNumbers{}, nil } func (s twilioService) SaveNewNumber(number viewmodel.Number) (viewmodel.Number, error) { phoneNumber := number.PhoneNumber if strings.Contains(phoneNumber, "+1") { phoneNumber = strings.Replace(phoneNumber, "+1", "", -1) } area := phoneNumber[0:3] msgData := url.Values{} msgData.Set("PhoneNumber", number.PhoneNumber) msgData.Set("AreaCode", area) msgData.Set("FriendlyName", number.FriendlyName) msgData.Set("SmsApplicationSid", s.cfg.Twilio.TwiMLSID) msgDataReader := *strings.NewReader(msgData.Encode()) client := &http.Client{} req, _ := http.NewRequest("POST", s.urlActualNumber, &msgDataReader) req.Header.Add("Accept", "application/json") req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.SetBasicAuth(s.cfg.Twilio.Account, s.cfg.Twilio.Token) resp, err := client.Do(req) if err != nil { fmt.Println("Error Twilio: ", err) } defer resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { decoder := json.NewDecoder(resp.Body) err := decoder.Decode(&number) if err != nil { return viewmodel.Number{}, err } return number, nil } else { return viewmodel.Number{}, errors.New(resp.Status) } } func (s twilioService) GetValidNumber(from string, to string) (viewmodel.Number, error) { list, err := s.GetAvaliableNumbers(to) if err != nil { return viewmodel.Number{}, err } if len(list.IncomingPhones) > 0 { return list.IncomingPhones[0], nil } list, err = s.GetAvaliableNewNumbers(to) if err != nil { return viewmodel.Number{}, err } if len(list.AvailableNumbers) > 0 { number, err := s.SaveNewNumber(list.AvailableNumbers[0]) if err != nil { fmt.Println("Error on GetValidNumber: ", err.Error()) return viewmodel.Number{}, err } return number, nil } else { return viewmodel.Number{}, errors.New("No new number available") } } func (s twilioService) GetNumber(from string, to string) (string, error) { number, err := s.GetValidNumber(from, to) if err != nil { return from, err } return number.PhoneNumber, nil } func (s twilioService) SendSMS(from string, to string, message string) error { if from == "" { return errors.New("TwilioService.SendSMS: from parameter is mandatory") } if to == "" { return errors.New("TwilioService.SendSMS: to parameter is mandatory") } if message == "" { return errors.New("TwilioService.SendSMS: message parameter is mandatory") } senderNumber, err := s.GetNumber(from, to) if err != nil { return err } msgData := url.Values{} msgData.Set("To", to) msgData.Set("From", senderNumber) msgData.Set("Body", message) msgDataReader := *strings.NewReader(msgData.Encode()) client := &http.Client{} req, _ := http.NewRequest("POST", s.urlMessage, &msgDataReader) req.SetBasicAuth(s.cfg.Twilio.Account, s.cfg.Twilio.Token) req.Header.Add("Accept", "application/json") req.Header.Add("Content-Type", "application/x-www-form-urlencoded") resp, err := client.Do(req) if err != nil { fmt.Println("Error Twilio: ", err) } defer resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { var data map[string]interface{} decoder := json.NewDecoder(resp.Body) err := decoder.Decode(&data) if err != nil { return err } } else { fmt.Println("Error on Twilio Response: ", resp.Status) return errors.New(resp.Status) } return nil }