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.

361 lines
8.7 KiB

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