Older/Server/WeChatContext/WeChatContext.cpp
2023-07-21 16:17:01 +08:00

185 lines
6.3 KiB
C++

#include "WeChatContext.h"
#include "../ServiceManager.h"
#include "BoostLog.h"
#include "WeChatSession.h"
#include <NetworkUtility.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/asio/defer.hpp>
#include <boost/beast/core.hpp>
#include <boost/format.hpp>
#include <boost/json/object.hpp>
#include <boost/json/parse.hpp>
#include <boost/json/serialize.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <sstream>
std::string WeChatContext::reply(const std::string &body) {
std::ostringstream oss;
LOG(info) << "someone send message: \n" << body;
boost::property_tree::ptree ptree;
std::istringstream iss(body);
boost::property_tree::read_xml(iss, ptree);
auto ToUserName = ptree.get_optional<std::string>("xml.ToUserName");
if (!ToUserName) {
LOG(error) << "request dont contain ToUserName.";
return oss.str();
}
auto FromUserName = ptree.get<std::string>("xml.FromUserName");
auto CreateTime = ptree.get<std::string>("xml.CreateTime");
auto MsgType = ptree.get<std::string>("xml.MsgType");
auto content = ptree.get<std::string>("xml.Content");
auto MsgId = ptree.get<std::string>("xml.MsgId");
std::shared_ptr<WeChatSession> session;
if (m_sessions.count(FromUserName) > 0) {
session = m_sessions.at(FromUserName);
} else {
session = std::make_shared<WeChatSession>(FromUserName);
m_sessions.emplace(FromUserName, session);
}
boost::algorithm::trim(content);
auto reply = session->processInput(content);
boost::property_tree::ptree sendXml;
sendXml.put("xml.Content", reply);
LOG(info) << "send " << FromUserName << ": " << reply;
sendXml.put("xml.ToUserName", FromUserName);
sendXml.put("xml.FromUserName", *ToUserName);
sendXml.put("xml.CreateTime", CreateTime);
sendXml.put("xml.MsgType", MsgType);
boost::property_tree::write_xml(oss, sendXml);
// LOG(info) << "reply content:\n " << oss.str();
return oss.str();
}
WeChatContext::WeChatContext(boost::asio::io_context &ioContext)
: m_ioContext(ioContext), m_timer(ioContext), m_sessionsExpireTimer(ioContext) {
boost::asio::defer([this]() { updateAccessToken(); });
}
void WeChatContext::updateAccessToken() {
boost::beast::error_code error;
boost::format target("/cgi-bin/token?grant_type=client_credential&appid=%1%&secret=%2%");
target % appid % secret;
auto response = Https::get(m_ioContext, host, port, target.str(), error);
if (error) {
LOG(error) << error.message();
return;
}
if (response.empty()) {
LOG(warning) << "response is empty.";
return;
}
auto json = boost::json::parse(response);
auto &accessTokenObject = json.as_object();
if (accessTokenObject.count("errcode")) {
LOG(error) << "get access_token failed,code: " << accessTokenObject.at("errcode").as_int64()
<< ", message: " << accessTokenObject.at("errmsg").as_string();
return;
}
m_accessToken = accessTokenObject.at("access_token").as_string();
auto expires_in = accessTokenObject.at("expires_in").as_int64();
// LOG(info) << "access_token: " << m_accessToken;
LOG(info) << "re-access_token after " << expires_in << " s.";
m_timer.expires_after(std::chrono::seconds(expires_in));
m_timer.async_wait([this](const boost::system::error_code &error) {
if (error) {
LOG(error) << error.message();
return;
}
updateAccessToken();
});
broadcast("hello,amass.");
}
WeChatContext::OpenIds WeChatContext::users() {
boost::beast::error_code error;
boost::format target("/cgi-bin/user/get?access_token=%1%");
target % m_accessToken;
auto response = Https::get(m_ioContext, host, port, target.str(), error);
if (error) {
LOG(error) << error.message();
return {};
}
auto json = boost::json::parse(response);
auto &responseObject = json.as_object();
if (responseObject.contains("errcode")) {
LOG(error) << responseObject.at("errmsg").as_string();
return {};
}
auto &users = responseObject.at("data").as_object().at("openid").as_array();
if (users.empty()) {
LOG(info) << "now we have no users.";
}
OpenIds ret;
for (auto &id : users) {
ret.emplace_back(id.as_string());
}
return ret;
}
std::string WeChatContext::broadcast(const std::string_view &message) {
boost::json::object messageObject;
auto users = this->users();
LOG(info) << "users: " << users;
if (users.size() < 2) users.emplace_back("fake_user");
boost::json::array usersArray;
for (auto &user : users) {
usersArray.emplace_back(user);
}
messageObject.emplace("touser", std::move(usersArray));
messageObject.emplace("msgtype", "text");
boost::json::object textObject;
textObject.emplace("content", message.data());
messageObject.emplace("text", std::move(textObject));
boost::format target("/cgi-bin/message/mass/send?access_token=%1%");
target % m_accessToken;
boost::system::error_code error;
auto response = Https::post(m_ioContext, host, port, target.str(), boost::json::serialize(messageObject), error);
if (error) {
// LOG(error) << error.message();
return response;
}
return response;
}
void WeChatContext::cleanExpiredSessions(const boost::system::error_code &error) {
if (error) {
LOG(error) << error.message();
return;
}
auto now = std::chrono::system_clock::now();
for (auto iterator = m_sessions.begin(); iterator != m_sessions.cend();) {
if (std::chrono::duration_cast<std::chrono::seconds>(now - iterator->second->lastAccessedTime()) >
sessionExpireTime) {
iterator = m_sessions.erase(iterator);
} else {
++iterator;
}
}
m_sessionsExpireTimer.expires_after(sessionExpireTime);
m_sessionsExpireTimer.async_wait([ptr{weak_from_this()}](const boost::system::error_code &error) {
if (ptr.expired()) return;
ptr.lock()->cleanExpiredSessions(error);
});
}