| @@ -6,7 +6,6 @@ import ( | |||
| "io/ioutil" | |||
| "log" | |||
| "os" | |||
| "time" | |||
| ) | |||
| //chat state that we might be use for collecting infomation from user | |||
| @@ -14,31 +13,23 @@ type chatState struct { | |||
| //back pointer style information, managed by general process | |||
| OpenID string `json:"OpenID"` //whom is this belongs to | |||
| Procedure string `json:"Procedure"` //which procedure this belongs to | |||
| Sent bool `json:"Sent"` //whether the message has been sent or not | |||
| Received bool `json:"Received"` //whether the expected message has been received or not | |||
| //below is managed by individual procedure | |||
| //below is managed by procedure only | |||
| //real state information | |||
| Name string `json:"Name"` //state name | |||
| Expire int32 `json:"Expire"` //unix timestamp when this state expire | |||
| Send struct { //anything we need to send? | |||
| Type string `json:"Type"` //what type of message | |||
| Message map[string]string `json:"Message"` //the message to be sent,key value pair to describe the message | |||
| } `json:"Send"` | |||
| Receive struct { //anything we expect to receive | |||
| Validator string `json:"Validator"` | |||
| Hint string `json:"Hint"` | |||
| Message map[string]string `json:"Message"` //the description for receiving message | |||
| } `json:"Receive"` | |||
| Name string `json:"Name"` //state name | |||
| Expire int32 `json:"Expire"` //unix timestamp when this state expire | |||
| //persistant sate | |||
| Save map[string]string `json:"Save"` //the state save some data for later usage | |||
| //runtime memory only | |||
| response string //被动回复消息的内容 | |||
| } | |||
| //for individual state | |||
| func getCurrentState(openID string, procedure string) (result chatState, err error) { | |||
| path := getProcedurePath(openID, procedure) | |||
| log.Printf("read state from %s\r\n", path) | |||
| //log.Printf("read state from %s\r\n", path) | |||
| body, err := ioutil.ReadFile(path) | |||
| if err != nil { //read file error | |||
| if isFileExist(path) { | |||
| @@ -51,9 +42,11 @@ func getCurrentState(openID string, procedure string) (result chatState, err err | |||
| log.Printf("Session Content [path=%s] not correct: ", path) | |||
| log.Println(err) | |||
| } | |||
| //we don't check Expire, we give the caller full control on | |||
| //how to deal wiht expired session | |||
| err = sanityCheckState(openID, procedure, result) | |||
| return | |||
| } | |||
| func sanityCheckState(openID, procedure string, result chatState) (err error) { | |||
| //check whether state is for the correct openID and procedure | |||
| if result.OpenID != openID { | |||
| err = errors.New("Error: State for " + openID + " is actually for " + result.OpenID) | |||
| @@ -103,70 +96,6 @@ type ValidationResult struct { | |||
| //Validator function type for validating all wechat inputs | |||
| type Validator func(s chatState) ValidationResult | |||
| //start a procedure | |||
| func startProcedure(openID, procedure string) (err error) { | |||
| //init procedure state | |||
| init := getProcedureInit(openID, procedure) | |||
| if init == nil { | |||
| msg := "FATAL: cannot initialize procedure [" + procedure + "] " | |||
| err = errors.New(msg) | |||
| return | |||
| } | |||
| //init and get initial state | |||
| state := init(openID) | |||
| //do the real concret work for processing the state | |||
| err = processProcedureState(state) | |||
| return | |||
| } | |||
| //resume a previous Procedure's state | |||
| func resumeProcedure(openID, procedure string) (err error) { | |||
| state, err := getCurrentState(openID, procedure) | |||
| if err != nil { | |||
| return | |||
| } | |||
| return processProcedureState(state) | |||
| } | |||
| //finish a procedure, regardless its been finished or not | |||
| //normally not finished normally | |||
| func stopProcedure(openID, procedure string) { | |||
| path := getProcedurePath(openID, procedure) | |||
| os.Remove(path) | |||
| log.Println("Clearing [" + openID + "] @ [" + procedure + "]") | |||
| } | |||
| func processProcedureState(state chatState) (err error) { | |||
| //send what we need to send | |||
| if isExpired(state.Expire) { | |||
| return errors.New("State has expired " + state.Name) | |||
| } | |||
| //mark we have sent. | |||
| //do we need input? waiting for input | |||
| func saveChatState(state chatState) { | |||
| //if not, what is next state | |||
| log.Println(state) | |||
| return | |||
| } | |||
| type initProcedureFunction func(openid string) (initState chatState) | |||
| func getProcedureInit(openID, procedure string) initProcedureFunction { | |||
| initFunc := map[string]initProcedureFunction{ | |||
| "TestDummy": nil, | |||
| "TestEcho": initTestEcho, | |||
| "GetBasicUserInfo": initGetBasicUserInfo, | |||
| "GetEmailAddr": initGetBasicUserInfo, | |||
| } | |||
| return initFunc[procedure] | |||
| } | |||
| func initTestEcho(openid string) (r chatState) { | |||
| r.Name = openid | |||
| r.Expire = int32(time.Now().Unix() + 100) | |||
| return | |||
| } | |||
| @@ -11,19 +11,11 @@ func TestChatState(t *testing.T) { | |||
| s := chatState{} | |||
| s.Name = "waiting for username" | |||
| s.Expire = int32(time.Now().Unix() + 200) | |||
| s.Send.Message = map[string]string{ | |||
| s.Save = map[string]string{ | |||
| "txt": "What is your date of birth?", | |||
| "icon": "/mnt/data/abc.jpg", | |||
| } | |||
| s.Send.Type = "text" | |||
| s.Sent = false | |||
| s.Received = false | |||
| s.Receive.Hint = "hint" | |||
| s.Receive.Validator = "validator email" | |||
| s.Receive.Message = map[string]string{ | |||
| "rtxt": "should be 3 chars at least", | |||
| "ricon": "icon path should be available", | |||
| } | |||
| s.response = "somexml less than 2018bytes" | |||
| //save | |||
| n, err := setCurrentState(openID, procedure, s) | |||
| @@ -35,15 +27,13 @@ func TestChatState(t *testing.T) { | |||
| //compare | |||
| AssertEqual(t, m.Name, n.Name, "Name should be equal") | |||
| AssertEqual(t, m.Expire, n.Expire, "Expire should be equal") | |||
| AssertEqual(t, m.Send.Type, n.Send.Type, "Send.Type should be equal") | |||
| AssertEqual(t, m.Received, false, "Receive.Received should be false") | |||
| AssertEqual(t, m.Sent, false, "Send.Sent should be false") | |||
| AssertEqual(t, m.Save["txt"], s.Save["txt"], "Message[txt] should be equal") | |||
| AssertEqual(t, m.Save["icon"], s.Save["icon"], "Message[icon] should be equal") | |||
| AssertEqual(t, m.OpenID, openID, "openID should be "+openID) | |||
| AssertEqual(t, m.response, "", "response should be empty") | |||
| AssertEqual(t, m.Procedure, procedure, "procedure should be "+procedure) | |||
| AssertEqual(t, m.Send.Message["txt"], n.Send.Message["txt"], "Message[txt] should be equal") | |||
| AssertEqual(t, m.Send.Message["icon"], n.Send.Message["icon"], "Message[icon] should be equal") | |||
| AssertEqual(t, m.Receive.Message["rtxt"], n.Receive.Message["rtxt"], "Message[rtxt] should be equal") | |||
| AssertEqual(t, m.Receive.Message["ricon"], n.Receive.Message["ricon"], "Message[ricon] should be equal") | |||
| AssertEqual(t, m.Save["txt"], n.Save["txt"], "Message[txt] should be equal") | |||
| AssertEqual(t, m.Save["icon"], n.Save["icon"], "Message[icon] should be equal") | |||
| err = deleteChatState(openID, procedure) | |||
| AssertEqual(t, err, nil, "delete chatState should be good") | |||
| @@ -12,7 +12,7 @@ func SetupConfig() { | |||
| "cmtWK2teRnLOXyO5dw7lJkETv9jCeNAqYyguEu5D8gG", | |||
| "wx876e233fde456b7b", | |||
| "4a91aa328569b10a9fb97adeb8b0af58", | |||
| "/tmp/wechat_hitxy_access_token", | |||
| "/tmp/wechat_hitxy_token", | |||
| "gh_f09231355c68"} | |||
| CRMConfig = EspoCRMAPIConfig{ | |||
| @@ -6,6 +6,7 @@ import ( | |||
| "io" | |||
| "io/ioutil" | |||
| "log" | |||
| "mime" | |||
| "net/http" | |||
| "net/url" | |||
| "os" | |||
| @@ -80,8 +81,10 @@ func saveHTTPRequest(req *http.Request) (tmpFile string, contentType string, err | |||
| log.Println(err) | |||
| return "", contentType, err | |||
| } | |||
| tmpFile = file.Name() | |||
| noSuffix := file.Name() | |||
| file.Close() | |||
| tmpFile = noSuffix + contentType2Suffix(contentType) | |||
| os.Rename(noSuffix, tmpFile) | |||
| //see if its a video url | |||
| if len < 4096 && contentType == "text/plain" { | |||
| @@ -104,3 +107,11 @@ func saveURLwithHTTPHeader(url string, headers map[string]string) (tmpFile strin | |||
| } | |||
| return saveHTTPRequest(req) | |||
| } | |||
| func contentType2Suffix(typ string) string { | |||
| exts, err := mime.ExtensionsByType(typ) | |||
| if err != nil { | |||
| return "" | |||
| } | |||
| return exts[0] | |||
| } | |||
| @@ -31,12 +31,13 @@ func TestSendVoice(t *testing.T) { | |||
| func TestSendVideo(t *testing.T) { | |||
| SetupConfig() | |||
| //kfSendVideo(toUser, "media_for_test/video.mp4", "测试时品", "普通描述", "media_for_test/music-thumb.jpg") | |||
| kfSendVideoByMediaID(toUser, | |||
| "xwcgPCY8TRHP_PIy_4qunL8ad9mq7vD3hc9-OpNVRKG1qTwjKkQHN4GKb9mAcJ3J", | |||
| "视频测试标题", | |||
| "视频测试描述信息", | |||
| "6QKTfDxkQS2ACDzVhY0ddKjlIsBTyB6cf9fFWG88uwbJ0Mlh_gSIMxnaGvdqU4y0") | |||
| kfSendVideo(toUser, "media_for_test/video.mp4", "测试时品", "普通描述", "media_for_test/music-thumb.jpg") | |||
| // kfSendVideoByMediaID(toUser, | |||
| // "xwcgPCY8TRHP_PIy_4qunL8ad9mq7vD3hc9-OpNVRKG1qTwjKkQHN4GKb9mAcJ3J", | |||
| // "视频测试标题", | |||
| // "视频测试描述信息", | |||
| // "6QKTfDxkQS2ACDzVhY0ddKjlIsBTyB6cf9fFWG88uwbJ0Mlh_gSIMxnaGvdqU4y0") | |||
| } | |||
| func TestSendMusic(t *testing.T) { | |||
| @@ -18,7 +18,7 @@ type Article struct { | |||
| } | |||
| //BuildTextMsg Given a text message send it to wechat client | |||
| func BuildTextMsg(txt string, ToUserName string) (xml string, err error) { | |||
| func BuildTextMsg(ToUserName string, txt string) (xml string, err error) { | |||
| if txt == "" || ToUserName == "" { | |||
| err = errors.New("Empty text body or Empty destination") | |||
| xml = "" | |||
| @@ -21,15 +21,15 @@ func proc000AskName(openid string) { | |||
| s.Name = "AskName" | |||
| s.Expire = 300 //5 minutes | |||
| s.Save = map[string]string{} //clear | |||
| s.Send.Message["q"] = "请输入您的真实中文名,没有请填写 ”无“ " | |||
| s.Receive.Validator = "validateChineseName" | |||
| } | |||
| func validateChineseName(s chatState) (r ValidationResult) { | |||
| r.accept = true | |||
| r.Error = "" | |||
| input := s.Receive.Message["name"] | |||
| //TODO | |||
| input := "abc" | |||
| // input := s.Receive.Message["name"] | |||
| r.Hint = "通常中文名只有三个字或者四个字,比如 王更新,诸葛亮,司马相如,慕容白雪" | |||
| if len(input) >= 10 { | |||
| r.accept = false | |||
| @@ -0,0 +1,104 @@ | |||
| package main | |||
| import ( | |||
| "errors" | |||
| "log" | |||
| "os" | |||
| "time" | |||
| ) | |||
| //start a procedure | |||
| func startProcedure(openID, procedure string) (err error) { | |||
| //init procedure state | |||
| init := getProcedureInit(openID, procedure) | |||
| if init == nil { | |||
| msg := "FATAL: cannot initialize procedure [" + procedure + "] " | |||
| err = errors.New(msg) | |||
| return | |||
| } | |||
| //init and get initial state | |||
| state := init(openID) | |||
| //save it | |||
| setCurrentState(openID, procedure, state) | |||
| //next is to waiting for user's input | |||
| //which may or may not happen very soon | |||
| return | |||
| } | |||
| //resume a previous Procedure's state | |||
| func resumeProcedure(openID, procedure string) (err error) { | |||
| state, err := getCurrentState(openID, procedure) | |||
| if err != nil { | |||
| return | |||
| } | |||
| //re-introduce what we are doing | |||
| // showProcIntro(openID, peocedure) | |||
| //tell user what has been achieved | |||
| // showProcSumary(openID, procedure) | |||
| return processProcedureState(state) | |||
| } | |||
| //finish a procedure, regardless its been finished or not | |||
| //normally not finished normally | |||
| func cleanProcedure(openID, procedure string) { | |||
| path := getProcedurePath(openID, procedure) | |||
| os.Remove(path) | |||
| log.Println("Clearing [" + openID + "] @ [" + procedure + "]") | |||
| } | |||
| func processProcedureState(state chatState) (err error) { | |||
| //send what we need to send | |||
| if isExpired(state.Expire) { | |||
| return errors.New("State has expired " + state.Name) | |||
| } | |||
| //mark we have sent. | |||
| //do we need input? waiting for input | |||
| //if not, what is next state | |||
| log.Println(state) | |||
| return | |||
| } | |||
| func getProcedureInit(openID, procedure string) initProcFunc { | |||
| initFunc := map[string]initProcFunc{ | |||
| "TestDummy": nil, | |||
| "TestEcho": initTestEcho, | |||
| "GetBasicUserInfo": initGetBasicUserInfo, | |||
| "GetEmailAddr": initGetBasicUserInfo, | |||
| } | |||
| return initFunc[procedure] | |||
| } | |||
| func initTestEcho(openid string) (r chatState) { | |||
| r.Name = openid | |||
| r.Expire = int32(time.Now().Unix() + 100) | |||
| return | |||
| } | |||
| //are we inside a procedure, and not finished? | |||
| func isInProc(openID string) (result bool, state chatState) { | |||
| r, err := getCurrentSesssion(openID) | |||
| if err != nil { | |||
| return false, state | |||
| } | |||
| if isExpired(r.Expire) { | |||
| return false, state | |||
| } | |||
| state, err = getCurrentState(openID, r.Procedure) | |||
| if err != nil || isExpired(state.Expire) { | |||
| return false, state | |||
| } | |||
| return true, state | |||
| } | |||
| //follow procedure, if there is any | |||
| func serveProc(openID string, input InWechatMsg) (next chatState) { | |||
| return | |||
| } | |||
| @@ -0,0 +1,60 @@ | |||
| package main | |||
| import ( | |||
| "fmt" | |||
| "log" | |||
| ) | |||
| func serveCommand(openID string, in InWechatMsg) (state chatState, processed bool) { | |||
| log.Println("process command") | |||
| return echoCommand(openID, in) | |||
| } | |||
| func echoCommand(openID string, in InWechatMsg) (state chatState, processed bool) { | |||
| processed = true | |||
| str, err := BuildTextMsg(openID, "default") | |||
| log.Println(in.header.MsgType) | |||
| switch in.body.(type) { | |||
| case TextMsg: | |||
| m := in.body.(TextMsg) | |||
| str, err = BuildTextMsg(openID, m.Content) | |||
| case PicMsg: | |||
| m := in.body.(PicMsg) | |||
| str = buildPicMsg(openID, m.MediaId) | |||
| case VoiceMsg: | |||
| m := in.body.(VoiceMsg) | |||
| str = buildVoiceMsg(openID, m.MediaId) | |||
| kfSendTxt(openID, "翻译结果:"+m.Recognition) | |||
| case VideoMsg: | |||
| m := in.body.(VideoMsg) | |||
| str = buildVideoMsg(openID, "e2iNEiSxCX5TV1WbFd0TQMn5lilY3bylh1--lDBwi7I", "航拍春日哈工大", m.MediaId) | |||
| case ShortVideoMsg: | |||
| m := in.body.(ShortVideoMsg) | |||
| str = buildVideoMsg(openID, "e2iNEiSxCX5TV1WbFd0TQMn5lilY3bylh1--lDBwi7I", "航拍春日哈工大", m.MediaId) | |||
| case LocationMsg: | |||
| m := in.body.(LocationMsg) | |||
| str, _ = BuildTextMsg(openID, fmt.Sprintf("long=%f, lat=%f, scale=%d", m.Location_X, m.Location_Y, m.Scale)) | |||
| case EventMsg: | |||
| m := in.body.(EventMsg) | |||
| log.Println(m) | |||
| url := fmt.Sprintf("https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&markers=color:red|label:S|%f,%f&zoom=12&size=414x736", m.Latitude, m.Longitude, m.Latitude, m.Longitude) | |||
| log.Println(url) | |||
| file, _, _ := saveURL(url) | |||
| str = buildUploadPicMsg(openID, file) | |||
| //str, _ = BuildTextMsg(openID, fmt.Sprintf("long=%f, lat=%f, scal =%f", m.Longitude, m.Latitude, m.Precision)) | |||
| default: | |||
| str, err = BuildTextMsg(openID, "text message") | |||
| } | |||
| state.OpenID = openID | |||
| state.Procedure = "" | |||
| state.response = str | |||
| log.Println(str) | |||
| if err != nil { | |||
| log.Println("build response failed") | |||
| processed = false | |||
| } | |||
| //state is any state that | |||
| return | |||
| } | |||
| @@ -33,8 +33,9 @@ func apiV1Main(w http.ResponseWriter, r *http.Request) { | |||
| case "GET": | |||
| answerInitialAuth(w, r) | |||
| default: | |||
| log.Fatalln(fmt.Sprintf("Unhandled HTTP %s", r.Method)) | |||
| fmt.Fprintf(w, "Protocol Error: Expect GET or POST only") | |||
| log.Println(fmt.Sprintf("FATAL: Unhandled HTTP %s", r.Method)) | |||
| response404Handler(w) | |||
| //fmt.Fprintf(w, "Protocol Error: Expect GET or POST only") | |||
| } | |||
| } | |||
| @@ -53,57 +54,81 @@ func answerInitialAuth(w http.ResponseWriter, r *http.Request) { | |||
| } | |||
| } | |||
| //answerWechatPost distribute PostRequest according to xml body info | |||
| // | |||
| //InWechatMsg what we received currently from wechat | |||
| type InWechatMsg struct { | |||
| header CommonHeader | |||
| body interface{} //dynamic type | |||
| } | |||
| // | |||
| //answerWechatPost distribute PostRequest according to xml body info | |||
| func answerWechatPost(w http.ResponseWriter, r *http.Request) { | |||
| body, _ := ioutil.ReadAll(r.Body) | |||
| d := decryptToXML(string(body)) | |||
| fmt.Printf("decrypt as: %s\n", d) | |||
| h := ReadCommonHeader(d) | |||
| reply, _ := BuildTextMsg(h.MsgType, h.FromUserName) | |||
| if h.MsgType == "text" { | |||
| // url := "http://www.google.com.au/" | |||
| // first := "很高兴有你参加志愿者" | |||
| // remark := "明天给你发1万块钱" | |||
| // name := "利于修" | |||
| // staffID := "1235465" | |||
| // joinDate := time.Now().Format("2006-01-02 15:04:06 Mon MST -07") | |||
| // totalCount := "50次" | |||
| // totalTime := "2小时" | |||
| // log.Println("send kf msg") | |||
| // templateSendJoinVolunteer(h.FromUserName, url, first, remark, name, staffID, joinDate, totalCount, totalTime) | |||
| reply, _ = BuildKFTransferAnyOneMsg(h.FromUserName) | |||
| //reply, _ = BuildKFTransferMsg(h.FromUserName, "kf2001@gh_f09231355c68") | |||
| //reply, _ = BuildTextMsg("test <a href=http://www.hitxy.org.au/> some link </a>", h.FromUserName) | |||
| } | |||
| if h.MsgType == "voice" { | |||
| a := ReadVoiceMsg(d) | |||
| reply, _ = BuildTextMsg(a.Recognition, h.FromUserName) | |||
| } | |||
| if h.MsgType == "event" { | |||
| a := ReadEventMsg(d) | |||
| if a.Event == "LOCATION" { | |||
| reply, _ = BuildTextMsg("test <a href=http://www.hitxy.org.au/> some link </a>", h.FromUserName) | |||
| fmt.Printf("output %s", reply) | |||
| in, valid := readWechatInput(r) | |||
| reply := "" //nothing | |||
| if !valid { | |||
| log.Println("Error: Invalid Input ") | |||
| } | |||
| //are we in an existing procedure | |||
| openID := in.header.FromUserName | |||
| yes, state := isInProc(openID) | |||
| if yes { | |||
| state := serveProc(openID, in) | |||
| reply = state.response | |||
| } else { | |||
| state, processed := serveCommand(openID, in) //search or other command | |||
| if !processed { // transfer to Customer Service (kf) | |||
| reply = buildKfForwardMsg(openID, "") | |||
| } else { | |||
| reply, _ = BuildTextMsg(a.Event+"/"+a.EventKey, h.FromUserName) | |||
| reply = state.response | |||
| } | |||
| //mediaID := "cU8BYvAEp3H25V-yGO3WBtMVk2bZcEBgf_kje7V-EPkRA_U4x-OAWb_ONg6Y-Qxt" //video | |||
| //mediaID := "e2iNEiSxCX5TV1WbFd0TQPTdMx8WbvpkOs_iNhSVQHY" // 236 second mp3 | |||
| //mediaID := "e2iNEiSxCX5TV1WbFd0TQDVyk970GxTcBWMnqc2RzF0" //5 sec mp3 | |||
| //mediaID := "e2iNEiSxCX5TV1WbFd0TQMqvVrqFDbDOacdjgQ-OAuE" //news | |||
| //reply = buildVideoMsg(h.FromUserName, mediaID, "标题", a.Event+"/"+a.EventKey) | |||
| //reply = buildUploadPicMsg(h.FromUserName, "media_for_test/640x480.jpg") | |||
| //reply = buildUploadVoiceMsg(h.FromUserName, "media_for_test/music.mp3") | |||
| //reply = buildVoiceMsg(h.FromUserName, mediaID) | |||
| reply = buildSampleMusicMsg(h.FromUserName) | |||
| //reply = buildSampleArticleMsg(h.FromUserName) | |||
| } | |||
| log.Println(reply) | |||
| w.Header().Set("Content-Type", "text/xml; charset=utf-8") | |||
| fmt.Fprint(w, reply) | |||
| saveChatState(state) | |||
| return | |||
| } | |||
| func readWechatInput(r *http.Request) (result InWechatMsg, valid bool) { | |||
| body, err := ioutil.ReadAll(r.Body) | |||
| if err != nil { | |||
| log.Println(err) | |||
| valid = false | |||
| return | |||
| } | |||
| d := decryptToXML(string(body)) | |||
| if d == "" { | |||
| log.Println("Cannot decode Message : \r\n" + string(body)) | |||
| valid = false | |||
| return | |||
| } | |||
| valid = true | |||
| fmt.Printf("decrypt as: %s\n", d) | |||
| result.header = ReadCommonHeader(d) | |||
| switch result.header.MsgType { | |||
| case "text": | |||
| result.body = ReadTextMsg(d) | |||
| case "image": | |||
| result.body = ReadPicMsg(d) | |||
| case "voice": | |||
| result.body = ReadVoiceMsg(d) | |||
| case "video": | |||
| result.body = ReadVideoMsg(d) | |||
| case "shortvideo": | |||
| result.body = ReadShortVideoMsg(d) | |||
| case "location": | |||
| result.body = ReadLocationMsg(d) | |||
| case "link": | |||
| result.body = ReadLinkMsg(d) | |||
| case "event": | |||
| result.body = ReadEventMsg(d) | |||
| default: | |||
| log.Println("Fatal: unknown incoming message type" + result.header.MsgType) | |||
| valid = false | |||
| } | |||
| return | |||
| } | |||
| @@ -113,7 +138,7 @@ func answerWechatPostEcho(w http.ResponseWriter, r *http.Request) { | |||
| s := ReadEncryptedMsg(string(body)) | |||
| //fmt.Printf("to decrypt %s", s.Encrypt) | |||
| d := Decode(s.Encrypt) | |||
| fmt.Printf("echo as: \n%s", d) | |||
| fmt.Printf("echo as: \r\n%s", d) | |||
| e := strings.Replace(d, "ToUserName", "FDDD20170506xyzunique", 2) | |||
| f := strings.Replace(e, "FromUserName", "ToUserName", 2) | |||