package work import ( "dashoo.cn/opms_libary/myerrors" "encoding/json" "errors" "fmt" "net/url" ) const ( //SysApprovalChange 审批实例变更 SysApprovalChange = "sys_approval_change" ) func marshalIntoJSONBody(x interface{}) ([]byte, error) { y, err := json.Marshal(x) if err != nil { // should never happen unless OOM or similar bad things return nil, fmt.Errorf("go-workwx: failed to marshal request: %w", err) } return y, nil } type reqAccessToken struct { CorpID string CorpSecret string } func (x reqAccessToken) intoURLValues() url.Values { return url.Values{ "corpid": {x.CorpID}, "corpsecret": {x.CorpSecret}, } } type respCommon struct { ErrCode int64 `json:"errcode"` ErrMsg string `json:"errmsg"` } // IsOK 响应体是否为一次成功请求的响应 // // 实现依据: https://work.weixin.qq.com/api/doc#10013 // // > 企业微信所有接口,返回包里都有errcode、errmsg。 // > 开发者需根据errcode是否为0判断是否调用成功(errcode意义请见全局错误码)。 // > 而errmsg仅作参考,后续可能会有变动,因此不可作为是否调用成功的判据。 func (x *respCommon) IsOK() bool { return x.ErrCode == 0 } func (x *respCommon) TryIntoErr() error { if x.IsOK() { return nil } return myerrors.ThirdPluginError(errors.New(fmt.Sprintf("ErrCode:%v, ErrMsg:%v", x.ErrCode, x.ErrMsg)), "企业微信接口调用失败") } type respAccessToken struct { respCommon AccessToken string `json:"access_token"` ExpiresInSecs int64 `json:"expires_in"` } type reqJSAPITicketAgentConfig struct{} func (x reqJSAPITicketAgentConfig) intoURLValues() url.Values { return url.Values{ "type": {"agent_config"}, } } type reqJSAPITicket struct{} func (x reqJSAPITicket) intoURLValues() url.Values { return url.Values{} } type respJSAPITicket struct { respCommon Ticket string `json:"ticket"` ExpiresInSecs int64 `json:"expires_in"` } // reqConvertUserIDToOpenID userid转openid 请求 type reqConvertUserIDToOpenID struct { UserID string `json:"userid"` } // respConvertUserIDToOpenID userid转openid 响应 type respConvertUserIDToOpenID struct { respCommon OpenID string `json:"openid"` } func (x reqConvertUserIDToOpenID) intoBody() ([]byte, error) { return marshalIntoJSONBody(x) } // reqConvertOpenIDToUserID openid转userid 请求 type reqConvertOpenIDToUserID struct { OpenID string `json:"openid"` } // respConvertUserIDToOpenID openid转userid 响应 type respConvertOpenIDToUserID struct { respCommon UserID string `json:"userid"` } func (x reqConvertOpenIDToUserID) intoBody() ([]byte, error) { return marshalIntoJSONBody(x) } // SizeType qrcode尺寸类型 // // 1: 171 x 171; 2: 399 x 399; 3: 741 x 741; 4: 2052 x 2052 type SizeType int const ( // SizeTypeMini 171 x 171 SizeTypeMini SizeType = iota + 1 // SizeTypeSmall 399 x 399 SizeTypeSmall // SizeTypeMedium 741 x 741 SizeTypeMedium // SizeTypeLarge 2052 x 2052 SizeTypeLarge ) // reqUserIDByMobile 手机号获取 userid 请求 type reqUserIDByMobile struct { Mobile string `json:"mobile"` } func (x reqUserIDByMobile) intoBody() ([]byte, error) { return marshalIntoJSONBody(x) } // respUserIDByMobile 手机号获取 userid 响应 type respUserIDByMobile struct { respCommon UserID string `json:"userid"` } // EmailType 用户邮箱的类型 // // 1表示用户邮箱是企业邮箱(默认) // 2表示用户邮箱是个人邮箱 type EmailType int const ( // EmailTypeCorporate 企业邮箱 EmailTypeCorporate EmailType = 1 // EmailTypePersonal 个人邮箱 EmailTypePersonal EmailType = 2 ) // reqUserIDByEmail 邮箱获取 userid 请求 type reqUserIDByEmail struct { Email string `json:"email"` EmailType EmailType `json:"email_type"` } func (x reqUserIDByEmail) intoBody() ([]byte, error) { return marshalIntoJSONBody(x) } // respUserIDByEmail 邮箱获取 userid 响应 type respUserIDByEmail struct { respCommon UserID string `json:"userid"` } // reqUserInfoGet 获取访问用户身份 type reqUserInfoGet struct { // 通过成员授权获取到的code,最大为512字节。每次成员授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。 Code string } func (x reqUserInfoGet) intoURLValues() url.Values { return url.Values{ "code": {x.Code}, } } // reqJSCode2Session 临时登录凭证校验 type reqJSCode2Session struct { JSCode string } func (x reqJSCode2Session) intoURLValues() url.Values { return url.Values{ "js_code": {x.JSCode}, "grant_type": {"authorization_code"}, } } // respJSCode2Session 临时登录凭证校验 type respJSCode2Session struct { respCommon JSCodeSession } // JSCodeSession 临时登录凭证 type JSCodeSession struct { CorpID string `json:"corpid"` UserID string `json:"userid"` SessionKey string `json:"session_key"` } // reqAuthCode2UserInfo 获取访问用户身份 type reqAuthCode2UserInfo struct { Code string } func (x reqAuthCode2UserInfo) intoURLValues() url.Values { return url.Values{ "code": {x.Code}, } } // respAuthCode2UserInfo 获取访问用户身份响应 type respAuthCode2UserInfo struct { respCommon AuthCodeUserInfo } // AuthCodeUserInfo 访问用户身份 type AuthCodeUserInfo struct { UserID string `json:"userid,omitempty"` UserTicket string `json:"user_ticket,omitempty"` OpenID string `json:"openid,omitempty"` ExternalUserID string `json:"external_userid,omitempty"` } type reqOAGetTemplateDetail struct { TemplateID string `json:"template_id"` } func (x reqOAGetTemplateDetail) intoBody() ([]byte, error) { return marshalIntoJSONBody(x) } type respOAGetTemplateDetail struct { respCommon OATemplateDetail } type reqOAApplyEvent struct { OAApplyEvent } func (x reqOAApplyEvent) intoBody() ([]byte, error) { return marshalIntoJSONBody(x) } type respOAApplyEvent struct { respCommon // SpNo 表单提交成功后,返回的表单编号 SpNo string `json:"sp_no"` } type reqOAGetApprovalInfo struct { StartTime string `json:"starttime"` EndTime string `json:"endtime"` Cursor int `json:"cursor"` Size uint32 `json:"size"` Filters []OAApprovalInfoFilter `json:"filters"` } func (x reqOAGetApprovalInfo) intoBody() ([]byte, error) { return marshalIntoJSONBody(x) } type respOAGetApprovalInfo struct { respCommon // SpNoList 审批单号列表,包含满足条件的审批申请 SpNoList []string `json:"sp_no_list"` } type reqOAGetApprovalDetail struct { // SpNo 审批单编号。 SpNo string `json:"sp_no"` } func (x reqOAGetApprovalDetail) intoBody() ([]byte, error) { return marshalIntoJSONBody(x) } type respOAGetApprovalDetail struct { respCommon // Info 审批申请详情 Info OAApprovalDetail `json:"info"` } // TaskCardBtn 任务卡片消息按钮 type TaskCardBtn struct { // Key 按钮key值,用户点击后,会产生任务卡片回调事件,回调事件会带上该key值,只能由数字、字母和“_-@”组成,最长支持128字节 Key string `json:"key"` // Name 按钮名称 Name string `json:"name"` // ReplaceName 点击按钮后显示的名称,默认为“已处理” ReplaceName string `json:"replace_name"` // Color 按钮字体颜色,可选“red”或者“blue”,默认为“blue” Color string `json:"color"` // IsBold 按钮字体是否加粗,默认false IsBold bool `json:"is_bold"` } // Article news 类型的文章 type Article struct { // 标题,不超过128个字节,超过会自动截断(支持id转译) Title string `json:"title"` // 描述,不超过512个字节,超过会自动截断(支持id转译) Description string `json:"description"` // 点击后跳转的链接。 最长2048字节,请确保包含了协议头(http/https),小程序或者url必须填写一个 URL string `json:"url"` // 图文消息的图片链接,最长2048字节,支持JPG、PNG格式,较好的效果为大图 1068*455,小图150*150 PicURL string `json:"picurl"` // 小程序appid,必须是与当前应用关联的小程序,appid和pagepath必须同时填写,填写后会忽略url字段 AppID string `json:"appid"` // 点击消息卡片后的小程序页面,最长128字节,仅限本小程序内的页面。appid和pagepath必须同时填写,填写后会忽略url字段 PagePath string `json:"pagepath"` } // MPArticle mpnews 类型的文章 type MPArticle struct { // 标题,不超过128个字节,超过会自动截断(支持id转译) Title string `json:"title"` // 图文消息缩略图的media_id, 可以通过素材管理接口获得。此处thumb_media_id即上传接口返回的media_id ThumbMediaID string `json:"thumb_media_id"` // 图文消息的作者,不超过64个字节 Author string `json:"author"` // 图文消息点击“阅读原文”之后的页面链接 ContentSourceURL string `json:"content_source_url"` // 图文消息的内容,支持html标签,不超过666 K个字节(支持id转译) Content string `json:"content"` // 图文消息的描述,不超过512个字节,超过会自动截断(支持id转译) Digest string `json:"digest"` } // Source 卡片来源样式信息,不需要来源样式可不填写 type Source struct { // 来源图片的url,来源图片的尺寸建议为72*72 IconURL string `json:"icon_url"` // 来源图片的描述,建议不超过20个字,(支持id转译) Desc string `json:"desc"` // 来源文字的颜色,目前支持:0(默认) 灰色,1 黑色,2 红色,3 绿色 DescColor int `json:"desc_color"` } // ActionList 操作列表,列表长度取值范围为 [1, 3] type ActionList struct { // 操作的描述文案 Text string `json:"text"` // 操作key值,用户点击后,会产生回调事件将本参数作为EventKey返回,回调事件会带上该key值,最长支持1024字节,不可重复 Key string `json:"key"` } // ActionMenu 卡片右上角更多操作按钮 type ActionMenu struct { // 更多操作界面的描述 Desc string `json:"desc"` ActionList []ActionList `json:"action_list"` } // MainTitle 一级标题 type MainTitle struct { // 一级标题,建议不超过36个字,文本通知型卡片本字段非必填,但不可本字段和sub_title_text都不填,(支持id转译) Title string `json:"title"` // 标题辅助信息,建议不超过160个字,(支持id转译) Desc string `json:"desc"` } // QuoteArea 引用文献样式 type QuoteArea struct { // 引用文献样式区域点击事件,0或不填代表没有点击事件,1 代表跳转url,2 代表跳转小程序 Type int `json:"type"` // 点击跳转的url,quote_area.type是1时必填 URL string `json:"url"` // 引用文献样式的标题 Title string `json:"title"` // 引用文献样式的引用文案 QuoteText string `json:"quote_text"` // 小程序appid,必须是与当前应用关联的小程序,appid和pagepath必须同时填写,填写后会忽略url字段 AppID string `json:"appid"` // 点击消息卡片后的小程序页面,最长128字节,仅限本小程序内的页面。appid和pagepath必须同时填写,填写后会忽略url字段 PagePath string `json:"pagepath"` } // EmphasisContent 关键数据样式 type EmphasisContent struct { // 关键数据样式的数据内容,建议不超过14个字 Title string `json:"title"` // 关键数据样式的数据描述内容,建议不超过22个字 Desc string `json:"desc"` } // HorizontalContentList 二级标题+文本列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过6 type HorizontalContentList struct { // 二级标题,建议不超过5个字 KeyName string `json:"keyname"` // 二级文本,如果horizontal_content_list.type是2,该字段代表文件名称(要包含文件类型),建议不超过30个字,(支持id转译) Value string `json:"value"` // 链接类型,0或不填代表不是链接,1 代表跳转url,2 代表下载附件,3 代表点击跳转成员详情 Type int `json:"type,omitempty"` // 链接跳转的url,horizontal_content_list.type是1时必填 URL string `json:"url,omitempty"` // 附件的media_id,horizontal_content_list.type是2时必填 MediaID string `json:"media_id,omitempty"` // 成员详情的userid,horizontal_content_list.type是3时必填 Userid string `json:"userid,omitempty"` } // JumpList 跳转指引样式的列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过3 type JumpList struct { // 跳转链接类型,0或不填代表不是链接,1 代表跳转url,2 代表跳转小程序 Type int `json:"type"` // 跳转链接样式的文案内容,建议不超过18个字 Title string `json:"title"` // 跳转链接的url,jump_list.type是1时必填 URL string `json:"url,omitempty"` // 跳转链接的小程序的appid,必须是与当前应用关联的小程序,jump_list.type是2时必填 Appid string `json:"appid,omitempty"` // 跳转链接的小程序的pagepath,jump_list.type是2时选填 PagePath string `json:"pagepath,omitempty"` } // CardAction 整体卡片的点击跳转事件,text_notice必填本字段 type CardAction struct { // 跳转事件类型,1 代表跳转url,2 代表打开小程序。text_notice卡片模版中该字段取值范围为[1,2] Type int `json:"type"` // 跳转事件的url,card_action.type是1时必填 URL string `json:"url"` // 跳转事件的小程序的appid,必须是与当前应用关联的小程序,card_action.type是2时必填 Appid string `json:"appid"` // 跳转事件的小程序的pagepath,card_action.type是2时选填 Pagepath string `json:"pagepath"` } // ImageTextArea 左图右文样式,news_notice类型的卡片,card_image和image_text_area两者必填一个字段,不可都不填 type ImageTextArea struct { // 左图右文样式区域点击事件,0或不填代表没有点击事件,1 代表跳转url,2 代表跳转小程序 Type int `json:"type"` // 点击跳转的url,image_text_area.type是1时必填 URL string `json:"url"` // 点击跳转的小程序的appid,必须是与当前应用关联的小程序,image_text_area.type是2时必填 AppID string `json:"appid,omitempty"` // 点击跳转的小程序的pagepath,image_text_area.type是2时选填 PagePath string `json:"pagepath,omitempty"` // 左图右文样式的标题 Title string `json:"title"` // 左图右文样式的描述 Desc string `json:"desc"` // 左图右文样式的图片url ImageURL string `json:"image_url"` } // CardImage 图片样式,news_notice类型的卡片,card_image和image_text_area两者必填一个字段,不可都不填 type CardImage struct { // 图片的url URL string `json:"url"` // 图片的宽高比,宽高比要小于2.25,大于1.3,不填该参数默认1.3 AspectRatio float32 `json:"aspect_ratio"` } // ButtonSelection 按钮交互型 type ButtonSelection struct { // 下拉式的选择器的key,用户提交选项后,会产生回调事件,回调事件会带上该key值表示该题,最长支持1024字节 QuestionKey string `json:"question_key"` // 下拉式的选择器的key,用户提交选项后,会产生回调事件,回调事件会带上该key值表示该题,最长支持1024字节 Title string `json:"title"` // 选项列表,下拉选项不超过 10 个,最少1个 OptionList []struct { // 下拉式的选择器选项的id,用户提交后,会产生回调事件,回调事件会带上该id值表示该选项,最长支持128字节,不可重复 ID string `json:"id"` // 下拉式的选择器选项的文案,建议不超过16个字 Text string `json:"text"` } `json:"option_list"` // 默认选定的id,不填或错填默认第一个 SelectedID string `json:"selected_id"` } type Button struct { // 按钮点击事件类型,0 或不填代表回调点击事件,1 代表跳转url Type int `json:"type,omitempty"` // 按钮文案,建议不超过10个字 Text string `json:"text"` // 按钮样式,目前可填1~4,不填或错填默认1 Style int `json:"style,omitempty"` // 按钮key值,用户点击后,会产生回调事件将本参数作为EventKey返回,回调事件会带上该key值,最长支持1024字节,不可重复,button_list.type是0时必填 Key string `json:"key,omitempty"` // 跳转事件的url,button_list.type是1时必填 URL string `json:"url,omitempty"` } // CheckBox 选择题样式 type CheckBox struct { // 选择题key值,用户提交选项后,会产生回调事件,回调事件会带上该key值表示该题,最长支持1024字节 QuestionKey string `json:"question_key"` // 选项list,选项个数不超过 20 个,最少1个 OptionList []struct { // 选项id,用户提交选项后,会产生回调事件,回调事件会带上该id值表示该选项,最长支持128字节,不可重复 ID string `json:"id"` // 选项文案描述,建议不超过17个字 Text string `json:"text"` // 该选项是否要默认选中 IsChecked bool `json:"is_checked"` } `json:"option_list" validate:"required,min=1,max=20"` // 选择题模式,单选:0,多选:1,不填默认0 Mode int `json:"mode" validate:"omitempty,oneof=0 1"` } // SubmitButton 提交按钮样式 type SubmitButton struct { // 按钮文案,建议不超过10个字,不填默认为提交 Text string `json:"text"` // 提交按钮的key,会产生回调事件将本参数作为EventKey返回,最长支持1024字节 Key string `json:"key"` } // SelectList 下拉式的选择器列表,multiple_interaction类型的卡片该字段不可为空,一个消息最多支持 3 个选择器 type SelectList struct { // 下拉式的选择器题目的key,用户提交选项后,会产生回调事件,回调事件会带上该key值表示该题,最长支持1024字节,不可重复 QuestionKey string `json:"question_key"` // 下拉式的选择器上面的title Title string `json:"title,omitempty"` // 默认选定的id,不填或错填默认第一个 SelectedID string `json:"selected_id,omitempty"` OptionList []OptionList `json:"option_list"` } // 项列表,下拉选项不超过 10 个,最少1个 type OptionList struct { // 下拉式的选择器选项的id,用户提交选项后,会产生回调事件,回调事件会带上该id值表示该选项,最长支持128字节,不可重复 ID string `json:"id"` // 下拉式的选择器选项的文案,建议不超过16个字 Text string `json:"text"` } // TemplateCardType 模板卡片的类型 type TemplateCardType string const ( CardTypeTextNotice TemplateCardType = "text_notice" // 文本通知型 CardTypeNewsNotice TemplateCardType = "news_notice" // 图文展示型 CardTypeButtonInteraction TemplateCardType = "button_interaction" // 按钮交互型 CardTypeVoteInteraction TemplateCardType = "vote_interaction" // 投票选择型 CardTypeMultipleInteraction TemplateCardType = "multiple_interaction" // 多项选择型 ) type TemplateCard struct { CardType TemplateCardType `json:"card_type"` Source Source `json:"source"` ActionMenu *ActionMenu `json:"action_menu,omitempty" validate:"required_with=TaskID"` TaskID string `json:"task_id,omitempty" validate:"required_with=ActionMenu"` MainTitle *MainTitle `json:"main_title"` QuoteArea *QuoteArea `json:"quote_area,omitempty"` // 文本通知型 EmphasisContent *EmphasisContent `json:"emphasis_content,omitempty"` SubTitleText string `json:"sub_title_text,omitempty"` // 图文展示型 ImageTextArea *ImageTextArea `json:"image_text_area,omitempty"` CardImage *CardImage `json:"card_image,omitempty"` HorizontalContentList []HorizontalContentList `json:"horizontal_content_list"` JumpList []JumpList `json:"jump_list"` CardAction *CardAction `json:"card_action,omitempty"` // 按钮交互型 ButtonSelection *ButtonSelection `json:"button_selection,omitempty"` ButtonList []Button `json:"button_list,omitempty" validate:"omitempty,max=6"` // 投票选择型 CheckBox *CheckBox `json:"checkbox,omitempty"` SelectList []SelectList `json:"select_list,omitempty" validate:"max=3"` SubmitButton *SubmitButton `json:"submit_button,omitempty"` } type TemplateCardUpdateMessage struct { UserIds []string `json:"userids" validate:"omitempty,max=100"` PartyIds []int64 `json:"partyids" validate:"omitempty,max=100"` TagIds []int32 `json:"tagids" validate:"omitempty,max=100"` AtAll int `json:"atall,omitempty"` ResponseCode string `json:"response_code"` Button struct { ReplaceName string `json:"replace_name"` } `json:"button" validate:"required_without=TemplateCard"` TemplateCard TemplateCard `json:"template_card" validate:"required_without=Button"` ReplaceText string `json:"replace_text,omitempty"` }