Files
2026-01-17 18:34:18 +08:00

286 lines
8.5 KiB
Lua

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