qywx.lua 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. local http = require("httpclient")
  2. local codec = require("fastweb.codec")
  3. local base64 = require("base64")
  4. local openssl = require("openssl")
  5. local M = {}
  6. local url_get_access_token = "https://qyapi.weixin.qq.com/cgi-bin/gettoken"
  7. local url_send_message = "https://qyapi.weixin.qq.com/cgi-bin/message/send"
  8. local url_get_userid_by_mobile = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserid"
  9. local url_get_userids = "https://qyapi.weixin.qq.com/cgi-bin/user/list_id"
  10. -- 验证签名
  11. M.verify_signature = function(token,timestamp,nonce,echostr)
  12. local params = {token, timestamp, nonce, echostr}
  13. table.sort(params, function(a, b) return tostring(a) < tostring(b) end)
  14. return string.lower(codec.sha1(table.concat(params)))
  15. end
  16. -- 解密数据
  17. M.decrypt_data = function(qywx_aeskey,data)
  18. -- 16随机数+4字节消息长度+消息体+接收ID
  19. local function parse_decrypted_msg(data)
  20. -- data: string, decrypted_echostr
  21. if #data < 20 then
  22. return nil, "data too short"
  23. end
  24. -- 跳过16字节随机数
  25. local msg_len_bytes = data:sub(17, 20)
  26. local b1, b2, b3, b4 = msg_len_bytes:byte(1, 4)
  27. local msg_len = b1 * 2^24 + b2 * 2^16 + b3 * 2^8 + b4
  28. local msg_start = 21
  29. local msg_end = 20 + msg_len
  30. local msg = data:sub(msg_start, msg_end)
  31. return msg
  32. end
  33. local cipher = openssl.cipher.new("aes-256-cbc")
  34. cipher:init(base64.decode(qywx_aeskey), "0123456789abcdef", false)
  35. local plaintext = cipher:update(data)
  36. plaintext = parse_decrypted_msg(plaintext)
  37. return plaintext
  38. end
  39. -- 获取access_token
  40. M.get_access_token = function(corpid,corpsecret)
  41. local url = url_get_access_token .. "?corpid=" .. corpid .. "&corpsecret=" .. corpsecret
  42. local result,err = http.get(url,{
  43. ["Content-Type"] = "application/json"
  44. })
  45. if not result then
  46. return false,err
  47. end
  48. local data = cjson.decode(err)
  49. if data.errcode == 0 then
  50. return true,data.access_token
  51. end
  52. return false,data.errmsg
  53. end
  54. -- 获取userid
  55. M.get_userid_by_mobile = function(access_token,mobile)
  56. local url = url_get_userid_by_mobile .. "?access_token=" .. access_token .. "&mobile=" .. mobile
  57. local result,err = http.post(url,{
  58. ["Content-Type"] = "application/json"
  59. },cjson.encode({
  60. mobile = mobile
  61. }))
  62. if not result then
  63. return false,err
  64. end
  65. local data = cjson.decode(err)
  66. if data.errcode == 0 then
  67. return true,data.userid
  68. end
  69. return false,data.errmsg
  70. end
  71. -- 获取用户列表
  72. M.get_userids = function(access_token)
  73. local url = url_get_userids .. "?access_token=" .. access_token
  74. local result,err = http.post(url,{
  75. ["Content-Type"] = "application/json"
  76. },cjson.encode({
  77. limit = 1000
  78. }))
  79. if not result then
  80. return false,err
  81. end
  82. local data = cjson.decode(err)
  83. if data.errcode == 0 then
  84. return true,data.dept_user
  85. end
  86. return false,data.errmsg
  87. end
  88. -- 上传临时文件
  89. M.upload_temp_file = function(access_token,data,type_str)
  90. local function make_upload_temp_data(data)
  91. local boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"
  92. local body = ""
  93. local filename = ""
  94. if type_str == "image" then
  95. filename = tostring(os.time())..".png"
  96. elseif type_str == "video" then
  97. filename = tostring(os.time())..".mp4"
  98. end
  99. -- 构造 multipart 数据
  100. body = body .. "--" .. boundary .. "\r\n"
  101. body = body .. "Content-Disposition: form-data; name=\"media\"; filename=\""..filename.."\"\r\n"
  102. body = body .. "Content-Type: application/octet-stream\r\n\r\n" .. data .. "\r\n"
  103. body = body .. "--" .. boundary .. "--\r\n"
  104. return body
  105. end
  106. local body = make_upload_temp_data(data)
  107. local url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=" .. access_token.."&type="..type_str
  108. local result,err = http.post(url,{
  109. ["Content-Type"] = "multipart/form-data; boundary=--WebKitFormBoundary7MA4YWxkTrZu0gW",
  110. ["Content-Length"] = #body
  111. },body)
  112. if not result then
  113. return false,err
  114. end
  115. local data = cjson.decode(err)
  116. if data.errcode == 0 then
  117. return true,data.media_id
  118. end
  119. return false,data.errmsg
  120. end
  121. -- 发送消息
  122. M.send_message = function(access_token,userid,agentid,message)
  123. local url = url_send_message .. "?access_token=" .. access_token
  124. local result,err = http.post(url,{
  125. ["Content-Type"] = "application/json"
  126. },cjson.encode({
  127. touser = userid,
  128. msgtype = "text",
  129. agentid = agentid,
  130. text = {
  131. content = message
  132. }
  133. }))
  134. if not result then
  135. return false,err
  136. end
  137. local data = cjson.decode(err)
  138. if data.errcode == 0 then
  139. return true
  140. end
  141. return false,data.errmsg
  142. end
  143. -- 发送图片消息
  144. M.send_image_message = function(access_token,userid,agentid,image_filepath)
  145. local file = io.open(image_filepath, "rb")
  146. if not file then
  147. return false, "cannot open file: " .. tostring(image_filepath)
  148. end
  149. local data = file:read("*a")
  150. file:close()
  151. local ok, media_id = M.upload_temp_file(access_token, data,"image")
  152. if not ok then
  153. return false, "upload_temp_file_failed:"..media_id
  154. end
  155. local url = url_send_message .. "?access_token=" .. access_token
  156. local result, err = http.post(url, {
  157. ["Content-Type"] = "application/json"
  158. }, cjson.encode({
  159. touser = userid,
  160. msgtype = "image",
  161. agentid = agentid,
  162. image = {
  163. media_id = media_id,
  164. }
  165. }))
  166. if not result then
  167. return false, err
  168. end
  169. local resp = cjson.decode(err)
  170. if resp.errcode == 0 then
  171. return true
  172. end
  173. return false, resp.errmsg
  174. end
  175. -- 发送视频消息
  176. M.send_video_message = function(access_token,userid,agentid,title,video_filepath)
  177. local file = io.open(video_filepath, "rb")
  178. if not file then
  179. return false, "cannot open file: " .. tostring(video_filepath)
  180. end
  181. local data = file:read("*a")
  182. file:close()
  183. local ok, media_id = M.upload_temp_file(access_token, data,"video")
  184. if not ok then
  185. return false, "upload_temp_file_failed:"..media_id
  186. end
  187. local url = url_send_message .. "?access_token=" .. access_token
  188. local result, err = http.post(url, {
  189. ["Content-Type"] = "application/json"
  190. }, cjson.encode({
  191. touser = userid,
  192. msgtype = "video",
  193. agentid = agentid,
  194. video = {
  195. media_id = media_id,
  196. title = title
  197. }
  198. }))
  199. if not result then
  200. return false, err
  201. end
  202. local resp = cjson.decode(err)
  203. if resp.errcode == 0 then
  204. return true
  205. end
  206. return false, resp.errmsg
  207. end
  208. -- 发送卡片消息
  209. M.send_card_message = function(access_token,userid,agentid,message)
  210. local url = url_send_message .. "?access_token=" .. access_token
  211. local result,err = http.post(url,{
  212. ["Content-Type"] = "application/json"
  213. },cjson.encode({
  214. touser = userid,
  215. msgtype = "news",
  216. agentid = agentid,
  217. news = {
  218. articles = {
  219. message
  220. }
  221. }
  222. }))
  223. if not result then
  224. return false,err
  225. end
  226. local data = cjson.decode(err)
  227. if data.errcode == 0 then
  228. return true
  229. end
  230. return false,data.errmsg
  231. end
  232. -- 发送卡片消息
  233. M.send_template_card_message = function(access_token,userid,agentid,message)
  234. local url = url_send_message .. "?access_token=" .. access_token
  235. local result,err = http.post(url,{
  236. ["Content-Type"] = "application/json"
  237. },cjson.encode({
  238. touser = userid,
  239. msgtype = "template_card",
  240. agentid = agentid,
  241. template_card = message
  242. }))
  243. if not result then
  244. return false,err
  245. end
  246. local data = cjson.decode(err)
  247. if data.errcode == 0 then
  248. return true
  249. end
  250. return false,data.errmsg
  251. end
  252. -- 发送MMD消息
  253. M.send_markdown_message = function(access_token,userid,agentid,message)
  254. local url = url_send_message .. "?access_token=" .. access_token
  255. local result,err = http.post(url,{
  256. ["Content-Type"] = "application/json"
  257. },cjson.encode({
  258. touser = userid,
  259. msgtype = "markdown",
  260. agentid = agentid,
  261. markdown = {
  262. content = message
  263. }
  264. }))
  265. if not result then
  266. return false,err
  267. end
  268. local data = cjson.decode(err)
  269. if data.errcode == 0 then
  270. return true
  271. end
  272. return false,data.errmsg
  273. end
  274. return M