local http = require("httpclient") local codec = require("fastweb.codec") local base64 = require("base64") local openssl = require("openssl") local M = {} local url_get_access_token = "https://qyapi.weixin.qq.com/cgi-bin/gettoken" local url_send_message = "https://qyapi.weixin.qq.com/cgi-bin/message/send" local url_get_userid_by_mobile = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserid" local url_get_userids = "https://qyapi.weixin.qq.com/cgi-bin/user/list_id" -- 验证签名 M.verify_signature = function(token,timestamp,nonce,echostr) local params = {token, timestamp, nonce, echostr} table.sort(params, function(a, b) return tostring(a) < tostring(b) end) return string.lower(codec.sha1(table.concat(params))) end -- 解密数据 M.decrypt_data = function(qywx_aeskey,data) -- 16随机数+4字节消息长度+消息体+接收ID local function parse_decrypted_msg(data) -- data: string, decrypted_echostr if #data < 20 then return nil, "data too short" end -- 跳过16字节随机数 local msg_len_bytes = data:sub(17, 20) local b1, b2, b3, b4 = msg_len_bytes:byte(1, 4) local msg_len = b1 * 2^24 + b2 * 2^16 + b3 * 2^8 + b4 local msg_start = 21 local msg_end = 20 + msg_len local msg = data:sub(msg_start, msg_end) return msg end local cipher = openssl.cipher.new("aes-256-cbc") cipher:init(base64.decode(qywx_aeskey), "0123456789abcdef", false) local plaintext = cipher:update(data) plaintext = parse_decrypted_msg(plaintext) return plaintext end -- 获取access_token M.get_access_token = function(corpid,corpsecret) local url = url_get_access_token .. "?corpid=" .. corpid .. "&corpsecret=" .. corpsecret local result,err = http.get(url,{ ["Content-Type"] = "application/json" }) if not result then return false,err end local data = cjson.decode(err) if data.errcode == 0 then return true,data.access_token end return false,data.errmsg end -- 获取userid M.get_userid_by_mobile = function(access_token,mobile) local url = url_get_userid_by_mobile .. "?access_token=" .. access_token .. "&mobile=" .. mobile local result,err = http.post(url,{ ["Content-Type"] = "application/json" },cjson.encode({ mobile = mobile })) if not result then return false,err end local data = cjson.decode(err) if data.errcode == 0 then return true,data.userid end return false,data.errmsg end -- 获取用户列表 M.get_userids = function(access_token) local url = url_get_userids .. "?access_token=" .. access_token local result,err = http.post(url,{ ["Content-Type"] = "application/json" },cjson.encode({ limit = 1000 })) if not result then return false,err end local data = cjson.decode(err) if data.errcode == 0 then return true,data.dept_user end return false,data.errmsg end -- 上传临时文件 M.upload_temp_file = function(access_token,data,type_str) local function make_upload_temp_data(data) local boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW" local body = "" local filename = "" if type_str == "image" then filename = tostring(os.time())..".png" elseif type_str == "video" then filename = tostring(os.time())..".mp4" end -- 构造 multipart 数据 body = body .. "--" .. boundary .. "\r\n" body = body .. "Content-Disposition: form-data; name=\"media\"; filename=\""..filename.."\"\r\n" body = body .. "Content-Type: application/octet-stream\r\n\r\n" .. data .. "\r\n" body = body .. "--" .. boundary .. "--\r\n" return body end local body = make_upload_temp_data(data) local url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=" .. access_token.."&type="..type_str local result,err = http.post(url,{ ["Content-Type"] = "multipart/form-data; boundary=--WebKitFormBoundary7MA4YWxkTrZu0gW", ["Content-Length"] = #body },body) if not result then return false,err end local data = cjson.decode(err) if data.errcode == 0 then return true,data.media_id end return false,data.errmsg end -- 发送消息 M.send_message = function(access_token,userid,agentid,message) local url = url_send_message .. "?access_token=" .. access_token local result,err = http.post(url,{ ["Content-Type"] = "application/json" },cjson.encode({ touser = userid, msgtype = "text", agentid = agentid, text = { content = message } })) if not result then return false,err end local data = cjson.decode(err) if data.errcode == 0 then return true end return false,data.errmsg end -- 发送图片消息 M.send_image_message = function(access_token,userid,agentid,image_filepath) local file = io.open(image_filepath, "rb") if not file then return false, "cannot open file: " .. tostring(image_filepath) end local data = file:read("*a") file:close() local ok, media_id = M.upload_temp_file(access_token, data,"image") if not ok then return false, "upload_temp_file_failed:"..media_id end local url = url_send_message .. "?access_token=" .. access_token local result, err = http.post(url, { ["Content-Type"] = "application/json" }, cjson.encode({ touser = userid, msgtype = "image", agentid = agentid, image = { media_id = media_id, } })) if not result then return false, err end local resp = cjson.decode(err) if resp.errcode == 0 then return true end return false, resp.errmsg end -- 发送视频消息 M.send_video_message = function(access_token,userid,agentid,title,video_filepath) local file = io.open(video_filepath, "rb") if not file then return false, "cannot open file: " .. tostring(video_filepath) end local data = file:read("*a") file:close() local ok, media_id = M.upload_temp_file(access_token, data,"video") if not ok then return false, "upload_temp_file_failed:"..media_id end local url = url_send_message .. "?access_token=" .. access_token local result, err = http.post(url, { ["Content-Type"] = "application/json" }, cjson.encode({ touser = userid, msgtype = "video", agentid = agentid, video = { media_id = media_id, title = title } })) if not result then return false, err end local resp = cjson.decode(err) if resp.errcode == 0 then return true end return false, resp.errmsg end -- 发送卡片消息 M.send_card_message = function(access_token,userid,agentid,message) local url = url_send_message .. "?access_token=" .. access_token local result,err = http.post(url,{ ["Content-Type"] = "application/json" },cjson.encode({ touser = userid, msgtype = "news", agentid = agentid, news = { articles = { message } } })) if not result then return false,err end local data = cjson.decode(err) if data.errcode == 0 then return true end return false,data.errmsg end -- 发送卡片消息 M.send_template_card_message = function(access_token,userid,agentid,message) local url = url_send_message .. "?access_token=" .. access_token local result,err = http.post(url,{ ["Content-Type"] = "application/json" },cjson.encode({ touser = userid, msgtype = "template_card", agentid = agentid, template_card = message })) if not result then return false,err end local data = cjson.decode(err) if data.errcode == 0 then return true end return false,data.errmsg end -- 发送MMD消息 M.send_markdown_message = function(access_token,userid,agentid,message) local url = url_send_message .. "?access_token=" .. access_token local result,err = http.post(url,{ ["Content-Type"] = "application/json" },cjson.encode({ touser = userid, msgtype = "markdown", agentid = agentid, markdown = { content = message } })) if not result then return false,err end local data = cjson.decode(err) if data.errcode == 0 then return true end return false,data.errmsg end return M