Quellcode durchsuchen

download file get correct suffix

master
Patrick Peng Sun vor 8 Jahren
Ursprung
Commit
1c003dd990
10 geänderte Dateien mit 280 neuen und 160 gelöschten Zeilen
  1. +13
    -84
      chatState.go
  2. +7
    -17
      chatState_test.go
  3. +1
    -1
      common_test.go
  4. +12
    -1
      download.go
  5. +7
    -6
      kfsend_test.go
  6. +1
    -1
      outMsg.go
  7. +3
    -3
      procGetBasicUserInfo.go
  8. +104
    -0
      procedure.go
  9. +60
    -0
      serveCommand.go
  10. +72
    -47
      server.go

+ 13
- 84
chatState.go Datei anzeigen

@@ -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
}

+ 7
- 17
chatState_test.go Datei anzeigen

@@ -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")

+ 1
- 1
common_test.go Datei anzeigen

@@ -12,7 +12,7 @@ func SetupConfig() {
"cmtWK2teRnLOXyO5dw7lJkETv9jCeNAqYyguEu5D8gG",
"wx876e233fde456b7b",
"4a91aa328569b10a9fb97adeb8b0af58",
"/tmp/wechat_hitxy_access_token",
"/tmp/wechat_hitxy_token",
"gh_f09231355c68"}

CRMConfig = EspoCRMAPIConfig{

+ 12
- 1
download.go Datei anzeigen

@@ -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]
}

+ 7
- 6
kfsend_test.go Datei anzeigen

@@ -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) {

+ 1
- 1
outMsg.go Datei anzeigen

@@ -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 = ""

+ 3
- 3
procGetBasicUserInfo.go Datei anzeigen

@@ -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

+ 104
- 0
procedure.go Datei anzeigen

@@ -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
}

+ 60
- 0
serveCommand.go Datei anzeigen

@@ -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
}

+ 72
- 47
server.go Datei anzeigen

@@ -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)

Laden…
Abbrechen
Speichern