本站源代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

362 lines
8.7KB

  1. // Copyright 2017 Lunny Xiao. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package dingtalk
  5. import (
  6. "bytes"
  7. "encoding/json"
  8. "errors"
  9. "fmt"
  10. "io/ioutil"
  11. "net/http"
  12. )
  13. /*
  14. {
  15. "msgtype": "text",
  16. "text": {
  17. "content": "我就是我, 是不一样的烟火"
  18. },
  19. "at": {
  20. "atMobiles": [
  21. "156xxxx8827",
  22. "189xxxx8325"
  23. ],
  24. "isAtAll": false
  25. }
  26. }
  27. {
  28. "msgtype": "link",
  29. "link": {
  30. "text": "这个即将发布的新版本,创始人陈航(花名“无招”)称它为“红树林”。
  31. 而在此之前,每当面临重大升级,产品经理们都会取一个应景的代号,这一次,为什么是“红树林”?",
  32. "title": "时代的火车向前开",
  33. "picUrl": "",
  34. "messageUrl": "https://mp.weixin.qq.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI"
  35. }
  36. }
  37. {
  38. "msgtype": "markdown",
  39. "markdown": {
  40. "title":"杭州天气",
  41. "text": "#### 杭州天气 @156xxxx8827\n" +
  42. "> 9度,西北风1级,空气良89,相对温度73%\n\n" +
  43. "> ![screenshot](http://image.jpg)\n" +
  44. "> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n"
  45. },
  46. "at": {
  47. "atMobiles": [
  48. "156xxxx8827",
  49. "189xxxx8325"
  50. ],
  51. "isAtAll": false
  52. }
  53. }
  54. {
  55. "actionCard": {
  56. "title": "乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身",
  57. "text": "![screenshot](@lADOpwk3K80C0M0FoA)
  58. ### 乔布斯 20 年前想打造的苹果咖啡厅
  59. Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划",
  60. "hideAvatar": "0",
  61. "btnOrientation": "0",
  62. "singleTitle" : "阅读全文",
  63. "singleURL" : "https://www.dingtalk.com/",
  64. "btns": [
  65. {
  66. "title": "内容不错",
  67. "actionURL": "https://www.dingtalk.com/"
  68. },
  69. {
  70. "title": "不感兴趣",
  71. "actionURL": "https://www.dingtalk.com/"
  72. }
  73. ]
  74. },
  75. "msgtype": "actionCard"
  76. }
  77. {
  78. "feedCard": {
  79. "links": [
  80. {
  81. "title": "时代的火车向前开",
  82. "messageURL": "https://mp.weixin.qq.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI",
  83. "picURL": "https://www.dingtalk.com/"
  84. },
  85. {
  86. "title": "时代的火车向前开2",
  87. "messageURL": "https://mp.weixin.qq.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI",
  88. "picURL": "https://www.dingtalk.com/"
  89. }
  90. ]
  91. },
  92. "msgtype": "feedCard"
  93. }
  94. */
  95. type LinkMsg struct {
  96. Title string `json:"title"`
  97. MessageURL string `json:"messageURL"`
  98. PicURL string `json:"picURL"`
  99. }
  100. type ActionCard struct {
  101. Text string `json:"text"`
  102. Title string `json:"title"`
  103. HideAvatar string `json:"hideAvatar"`
  104. BtnOrientation string `json:"btnOrientation"`
  105. SingleTitle string `json:"singleTitle"`
  106. SingleURL string `json:"singleURL"`
  107. Buttons []struct {
  108. Title string `json:"title"`
  109. ActionURL string `json:"actionURL"`
  110. } `json:"btns"`
  111. }
  112. // Payload struct
  113. type Payload struct {
  114. MsgType string `json:"msgtype"`
  115. Text struct {
  116. Content string `json:"content"`
  117. } `json:"text"`
  118. Link struct {
  119. Text string `json:"text"`
  120. Title string `json:"title"`
  121. PicURL string `json:"picUrl"`
  122. MessageURL string `json:"messageUrl"`
  123. } `json:"link"`
  124. Markdown struct {
  125. Text string `json:"text"`
  126. Title string `json:"title"`
  127. } `json:"markdown"`
  128. ActionCard ActionCard `json:"actionCard"`
  129. FeedCard struct {
  130. Links []LinkMsg `json:"links"`
  131. } `json:"feedCard"`
  132. At struct {
  133. AtMobiles []string `json:"atMobiles"`
  134. IsAtAll bool `json:"isAtAll"`
  135. } `json:"at"`
  136. }
  137. type Webhook struct {
  138. accessToken string
  139. }
  140. func NewWebhook(accessToken string) *Webhook {
  141. return &Webhook{accessToken}
  142. }
  143. type Response struct {
  144. ErrorCode int `json:"errcode"`
  145. ErrorMessage string `json:"errmsg"`
  146. }
  147. // SendPayload 发送消息
  148. func (w *Webhook) SendPayload(payload *Payload) error {
  149. bs, err := json.Marshal(payload)
  150. if err != nil {
  151. return err
  152. }
  153. resp, err := http.Post("https://oapi.dingtalk.com/robot/send?access_token="+w.accessToken, "application/json", bytes.NewReader(bs))
  154. if err != nil {
  155. return err
  156. }
  157. bs, err = ioutil.ReadAll(resp.Body)
  158. if err != nil {
  159. return err
  160. }
  161. if resp.StatusCode != 200 {
  162. return fmt.Errorf("%d: %s", resp.StatusCode, string(bs))
  163. }
  164. var result Response
  165. err = json.Unmarshal(bs, &result)
  166. if err != nil {
  167. return err
  168. }
  169. if result.ErrorCode != 0 {
  170. return fmt.Errorf("%d: %s", result.ErrorCode, result.ErrorMessage)
  171. }
  172. return nil
  173. }
  174. // SendTextMsg 发送文本消息
  175. func (w *Webhook) SendTextMsg(content string, isAtAll bool, mobiles ...string) error {
  176. return w.SendPayload(&Payload{
  177. MsgType: "text",
  178. Text: struct {
  179. Content string `json:"content"`
  180. }{
  181. Content: content,
  182. },
  183. At: struct {
  184. AtMobiles []string `json:"atMobiles"`
  185. IsAtAll bool `json:"isAtAll"`
  186. }{
  187. AtMobiles: mobiles,
  188. IsAtAll: isAtAll,
  189. },
  190. })
  191. }
  192. // SendLinkMsg 发送链接消息
  193. func (w *Webhook) SendLinkMsg(title, content, picURL, msgURL string) error {
  194. return w.SendPayload(&Payload{
  195. MsgType: "link",
  196. Link: struct {
  197. Text string `json:"text"`
  198. Title string `json:"title"`
  199. PicURL string `json:"picUrl"`
  200. MessageURL string `json:"messageUrl"`
  201. }{
  202. Text: content,
  203. Title: title,
  204. PicURL: picURL,
  205. MessageURL: msgURL,
  206. },
  207. })
  208. }
  209. // SendMarkdownMsg 发送markdown消息,仅支持以下格式
  210. /*
  211. 标题
  212. # 一级标题
  213. ## 二级标题
  214. ### 三级标题
  215. #### 四级标题
  216. ##### 五级标题
  217. ###### 六级标题
  218. 引用
  219. > A man who stands for nothing will fall for anything.
  220. 文字加粗、斜体
  221. **bold**
  222. *italic*
  223. 链接
  224. [this is a link](http://name.com)
  225. 图片
  226. ![](http://name.com/pic.jpg)
  227. 无序列表
  228. - item1
  229. - item2
  230. 有序列表
  231. 1. item1
  232. 2. item2
  233. */
  234. func (w *Webhook) SendMarkdownMsg(title, content string, isAtAll bool, mobiles ...string) error {
  235. return w.SendPayload(&Payload{
  236. MsgType: "markdown",
  237. Markdown: struct {
  238. Text string `json:"text"`
  239. Title string `json:"title"`
  240. }{
  241. Text: content,
  242. Title: title,
  243. },
  244. At: struct {
  245. AtMobiles []string `json:"atMobiles"`
  246. IsAtAll bool `json:"isAtAll"`
  247. }{
  248. AtMobiles: mobiles,
  249. IsAtAll: isAtAll,
  250. },
  251. })
  252. }
  253. // SendSingleActionCardMsg 发送整体跳转ActionCard类型消息
  254. func (w *Webhook) SendSingleActionCardMsg(title, content, linkTitle, linkURL string, hideAvatar, btnOrientation bool) error {
  255. var strHideAvatar = "0"
  256. if hideAvatar {
  257. strHideAvatar = "1"
  258. }
  259. var strBtnOrientation = "0"
  260. if btnOrientation {
  261. strBtnOrientation = "1"
  262. }
  263. return w.SendPayload(&Payload{
  264. MsgType: "actionCard",
  265. ActionCard: ActionCard{
  266. Text: content,
  267. Title: title,
  268. HideAvatar: strHideAvatar,
  269. BtnOrientation: strBtnOrientation,
  270. SingleTitle: linkTitle,
  271. SingleURL: linkURL,
  272. },
  273. })
  274. }
  275. // SendActionCardMsg 独立跳转ActionCard类型
  276. func (w *Webhook) SendActionCardMsg(title, content string, linkTitles, linkURLs []string, hideAvatar, btnOrientation bool) error {
  277. if len(linkTitles) == 0 || len(linkURLs) == 0 {
  278. return errors.New("链接参数不能为空")
  279. }
  280. if len(linkTitles) != len(linkURLs) {
  281. return errors.New("链接数量不匹配")
  282. }
  283. var strHideAvatar = "0"
  284. if hideAvatar {
  285. strHideAvatar = "1"
  286. }
  287. var strBtnOrientation = "0"
  288. if btnOrientation {
  289. strBtnOrientation = "1"
  290. }
  291. var btns []struct {
  292. Title string `json:"title"`
  293. ActionURL string `json:"actionURL"`
  294. }
  295. for i := 0; i < len(linkTitles); i++ {
  296. btns = append(btns, struct {
  297. Title string `json:"title"`
  298. ActionURL string `json:"actionURL"`
  299. }{
  300. Title: linkTitles[i],
  301. ActionURL: linkURLs[i],
  302. })
  303. }
  304. return w.SendPayload(&Payload{
  305. MsgType: "actionCard",
  306. ActionCard: ActionCard{
  307. Text: content,
  308. Title: title,
  309. HideAvatar: strHideAvatar,
  310. BtnOrientation: strBtnOrientation,
  311. Buttons: btns,
  312. },
  313. })
  314. }
  315. // SendLinkCardMsg 发送链接消息
  316. func (w *Webhook) SendLinkCardMsg(msgs []LinkMsg) error {
  317. return w.SendPayload(&Payload{
  318. MsgType: "feedCard",
  319. FeedCard: struct {
  320. Links []LinkMsg `json:"links"`
  321. }{
  322. Links: msgs,
  323. },
  324. })
  325. }
上海开阖软件有限公司 沪ICP备12045867号-1