| "io/ioutil" | "io/ioutil" | ||||
| "log" | "log" | ||||
| "os" | "os" | ||||
| "time" | |||||
| ) | ) | ||||
| //chat state that we might be use for collecting infomation from user | //chat state that we might be use for collecting infomation from user | ||||
| //back pointer style information, managed by general process | //back pointer style information, managed by general process | ||||
| OpenID string `json:"OpenID"` //whom is this belongs to | OpenID string `json:"OpenID"` //whom is this belongs to | ||||
| Procedure string `json:"Procedure"` //which procedure 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 | //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 | Save map[string]string `json:"Save"` //the state save some data for later usage | ||||
| //runtime memory only | |||||
| response string //被动回复消息的内容 | |||||
| } | } | ||||
| //for individual state | //for individual state | ||||
| func getCurrentState(openID string, procedure string) (result chatState, err error) { | func getCurrentState(openID string, procedure string) (result chatState, err error) { | ||||
| path := getProcedurePath(openID, procedure) | 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) | body, err := ioutil.ReadFile(path) | ||||
| if err != nil { //read file error | if err != nil { //read file error | ||||
| if isFileExist(path) { | if isFileExist(path) { | ||||
| log.Printf("Session Content [path=%s] not correct: ", path) | log.Printf("Session Content [path=%s] not correct: ", path) | ||||
| log.Println(err) | 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 | //check whether state is for the correct openID and procedure | ||||
| if result.OpenID != openID { | if result.OpenID != openID { | ||||
| err = errors.New("Error: State for " + openID + " is actually for " + result.OpenID) | err = errors.New("Error: State for " + openID + " is actually for " + result.OpenID) | ||||
| //Validator function type for validating all wechat inputs | //Validator function type for validating all wechat inputs | ||||
| type Validator func(s chatState) ValidationResult | 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 | |||||
| } | } |
| s := chatState{} | s := chatState{} | ||||
| s.Name = "waiting for username" | s.Name = "waiting for username" | ||||
| s.Expire = int32(time.Now().Unix() + 200) | 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?", | "txt": "What is your date of birth?", | ||||
| "icon": "/mnt/data/abc.jpg", | "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 | //save | ||||
| n, err := setCurrentState(openID, procedure, s) | n, err := setCurrentState(openID, procedure, s) | ||||
| //compare | //compare | ||||
| AssertEqual(t, m.Name, n.Name, "Name should be equal") | AssertEqual(t, m.Name, n.Name, "Name should be equal") | ||||
| AssertEqual(t, m.Expire, n.Expire, "Expire 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.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.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) | err = deleteChatState(openID, procedure) | ||||
| AssertEqual(t, err, nil, "delete chatState should be good") | AssertEqual(t, err, nil, "delete chatState should be good") |
| "cmtWK2teRnLOXyO5dw7lJkETv9jCeNAqYyguEu5D8gG", | "cmtWK2teRnLOXyO5dw7lJkETv9jCeNAqYyguEu5D8gG", | ||||
| "wx876e233fde456b7b", | "wx876e233fde456b7b", | ||||
| "4a91aa328569b10a9fb97adeb8b0af58", | "4a91aa328569b10a9fb97adeb8b0af58", | ||||
| "/tmp/wechat_hitxy_access_token", | |||||
| "/tmp/wechat_hitxy_token", | |||||
| "gh_f09231355c68"} | "gh_f09231355c68"} | ||||
| CRMConfig = EspoCRMAPIConfig{ | CRMConfig = EspoCRMAPIConfig{ |
| "io" | "io" | ||||
| "io/ioutil" | "io/ioutil" | ||||
| "log" | "log" | ||||
| "mime" | |||||
| "net/http" | "net/http" | ||||
| "net/url" | "net/url" | ||||
| "os" | "os" | ||||
| log.Println(err) | log.Println(err) | ||||
| return "", contentType, err | return "", contentType, err | ||||
| } | } | ||||
| tmpFile = file.Name() | |||||
| noSuffix := file.Name() | |||||
| file.Close() | file.Close() | ||||
| tmpFile = noSuffix + contentType2Suffix(contentType) | |||||
| os.Rename(noSuffix, tmpFile) | |||||
| //see if its a video url | //see if its a video url | ||||
| if len < 4096 && contentType == "text/plain" { | if len < 4096 && contentType == "text/plain" { | ||||
| } | } | ||||
| return saveHTTPRequest(req) | return saveHTTPRequest(req) | ||||
| } | } | ||||
| func contentType2Suffix(typ string) string { | |||||
| exts, err := mime.ExtensionsByType(typ) | |||||
| if err != nil { | |||||
| return "" | |||||
| } | |||||
| return exts[0] | |||||
| } |
| func TestSendVideo(t *testing.T) { | func TestSendVideo(t *testing.T) { | ||||
| SetupConfig() | 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) { | func TestSendMusic(t *testing.T) { |
| } | } | ||||
| //BuildTextMsg Given a text message send it to wechat client | //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 == "" { | if txt == "" || ToUserName == "" { | ||||
| err = errors.New("Empty text body or Empty destination") | err = errors.New("Empty text body or Empty destination") | ||||
| xml = "" | xml = "" |
| s.Name = "AskName" | s.Name = "AskName" | ||||
| s.Expire = 300 //5 minutes | s.Expire = 300 //5 minutes | ||||
| s.Save = map[string]string{} //clear | s.Save = map[string]string{} //clear | ||||
| s.Send.Message["q"] = "请输入您的真实中文名,没有请填写 ”无“ " | |||||
| s.Receive.Validator = "validateChineseName" | |||||
| } | } | ||||
| func validateChineseName(s chatState) (r ValidationResult) { | func validateChineseName(s chatState) (r ValidationResult) { | ||||
| r.accept = true | r.accept = true | ||||
| r.Error = "" | r.Error = "" | ||||
| input := s.Receive.Message["name"] | |||||
| //TODO | |||||
| input := "abc" | |||||
| // input := s.Receive.Message["name"] | |||||
| r.Hint = "通常中文名只有三个字或者四个字,比如 王更新,诸葛亮,司马相如,慕容白雪" | r.Hint = "通常中文名只有三个字或者四个字,比如 王更新,诸葛亮,司马相如,慕容白雪" | ||||
| if len(input) >= 10 { | if len(input) >= 10 { | ||||
| r.accept = false | r.accept = false |
| 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 | |||||
| } |
| 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 | |||||
| } |
| case "GET": | case "GET": | ||||
| answerInitialAuth(w, r) | answerInitialAuth(w, r) | ||||
| default: | 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") | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| //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) { | 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 { | } 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") | w.Header().Set("Content-Type", "text/xml; charset=utf-8") | ||||
| fmt.Fprint(w, reply) | 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 | return | ||||
| } | } | ||||
| s := ReadEncryptedMsg(string(body)) | s := ReadEncryptedMsg(string(body)) | ||||
| //fmt.Printf("to decrypt %s", s.Encrypt) | //fmt.Printf("to decrypt %s", s.Encrypt) | ||||
| d := Decode(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) | e := strings.Replace(d, "ToUserName", "FDDD20170506xyzunique", 2) | ||||
| f := strings.Replace(e, "FromUserName", "ToUserName", 2) | f := strings.Replace(e, "FromUserName", "ToUserName", 2) |