package godingtalk import ( "encoding/json" "fmt" "io" "net/http" "net/url" "time" ) const ( //VERSION is SDK version VERSION = "0.3" ) //DingTalkClient is the Client to access DingTalk Open API type DingTalkClient struct { AppKey string AppSecret string AgentID string PartnerID string AccessToken string HTTPClient *http.Client Cache Cache } //Unmarshallable is type Unmarshallable interface { checkError() error getWriter() io.Writer } //OAPIResponse is type OAPIResponse struct { ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"` } func (data *OAPIResponse) checkError() (err error) { if data.ErrCode != 0 { err = fmt.Errorf("%d: %s", data.ErrCode, data.ErrMsg) } return err } func (data *OAPIResponse) getWriter() io.Writer { return nil } //MessageResponse is type MessageResponse struct { OAPIResponse MessageID string `json:"messageId"` } //MessageResponse is type MessageReadListResponse struct { OAPIResponse NextCursor int64 `json:"next_cursor"` ReadUserIdList []string `json:"readUserIdList"` } //AccessTokenResponse is type AccessTokenResponse struct { OAPIResponse AccessToken string `json:"access_token"` Expires int `json:"expires_in"` Created int64 } //CreatedAt is when the access token is generated func (e *AccessTokenResponse) CreatedAt() int64 { return e.Created } //ExpiresIn is how soon the access token is expired func (e *AccessTokenResponse) ExpiresIn() int { return e.Expires } //JsAPITicketResponse is type JsAPITicketResponse struct { OAPIResponse Ticket string Expires int `json:"expires_in"` Created int64 } //CreatedAt is when the ticket is generated func (e *JsAPITicketResponse) CreatedAt() int64 { return e.Created } //ExpiresIn is how soon the ticket is expired func (e *JsAPITicketResponse) ExpiresIn() int { return e.Expires } //NewDingTalkClient creates a DingTalkClient instance func NewDingTalkClient(appkey, appsecret string) *DingTalkClient { c := new(DingTalkClient) c.AppKey = appkey c.AppSecret = appsecret c.HTTPClient = &http.Client{ Timeout: 30 * time.Second, } c.Cache = NewFileCache(".auth_file") return c } //RefreshAccessToken is to get a valid access token func (c *DingTalkClient) RefreshAccessToken() error { var data AccessTokenResponse err := c.Cache.Get(&data) if err == nil { c.AccessToken = data.AccessToken return nil } params := url.Values{} params.Add("appkey", c.AppKey) params.Add("appsecret", c.AppSecret) err = c.httpRPC("gettoken", params, nil, &data) if err == nil { c.AccessToken = data.AccessToken data.Expires = data.Expires | 7200 data.Created = time.Now().Unix() err = c.Cache.Set(&data) } return err } //GetJsAPITicket is to get a valid ticket for JS API func (c *DingTalkClient) GetJsAPITicket() (ticket string, err error) { var data JsAPITicketResponse cache := NewFileCache(".jsapi_ticket") err = cache.Get(&data) if err == nil { return data.Ticket, err } err = c.httpRPC("get_jsapi_ticket", nil, nil, &data) if err == nil { ticket = data.Ticket cache.Set(&data) } return ticket, err } //GetConfig is to return config in json func (c *DingTalkClient) GetConfig(nonceStr string, timestamp string, url string) string { ticket, _ := c.GetJsAPITicket() config := map[string]string{ "url": url, "nonceStr": nonceStr, "agentId": c.AgentID, "timeStamp": timestamp, "corpId": c.AppKey, "ticket": ticket, "signature": Sign(ticket, nonceStr, timestamp, url), } bytes, _ := json.Marshal(&config) return string(bytes) } //Sign is 签名 func Sign(ticket string, nonceStr string, timeStamp string, url string) string { s := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%s&url=%s", ticket, nonceStr, timeStamp, url) return sha1Sign(s) }