实现live2d接口。
This commit is contained in:
128
WeChat/Corporation/Context.cpp
Normal file
128
WeChat/Corporation/Context.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
#include "Context.h"
|
||||
#include "Base/Messages.h"
|
||||
#include "Core/Logger.h"
|
||||
#include "Core/MessageManager.h"
|
||||
#include "Http/Utility.h"
|
||||
#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>
|
||||
|
||||
namespace WeChat {
|
||||
namespace Corporation {
|
||||
Context::Context(boost::asio::io_context &ioContext) : m_ioContext(ioContext), m_timer(ioContext) {
|
||||
using namespace Core;
|
||||
auto manager = Singleton<MessageManager>::instance();
|
||||
if (manager) {
|
||||
manager->subscribe<NotifyServerChan>(
|
||||
[this](const boost::beast::http::request<boost::beast::http::string_body> &request) { notify(request); });
|
||||
}
|
||||
}
|
||||
|
||||
void Context::sendMessage(MessageType type, const std::string &message) {
|
||||
boost::format target("/cgi-bin/message/send?access_token=%1%");
|
||||
target % m_accessToken;
|
||||
|
||||
boost::json::object msg;
|
||||
msg["content"] = message;
|
||||
|
||||
boost::json::object request;
|
||||
request["touser"] = "@all";
|
||||
request["agentid"] = agentid;
|
||||
if (type == MessageType::Markdown) {
|
||||
request["msgtype"] = "markdown";
|
||||
request["markdown"] = std::move(msg);
|
||||
} else {
|
||||
request["msgtype"] = "text";
|
||||
request["text"] = std::move(msg);
|
||||
}
|
||||
auto body = boost::json::serialize(request);
|
||||
|
||||
boost::beast::error_code error;
|
||||
auto response = Https::post(m_ioContext, host, port, target.str(), body, error);
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
LOG(info) << response;
|
||||
}
|
||||
|
||||
void Context::start() {
|
||||
boost::asio::defer(m_ioContext, [ptr{weak_from_this()}]() {
|
||||
if (ptr.expired()) {
|
||||
LOG(error) << "Context instance was expired";
|
||||
return;
|
||||
}
|
||||
auto self = ptr.lock();
|
||||
self->updateAccessToken();
|
||||
});
|
||||
}
|
||||
|
||||
void Context::notify(const RequestType &request) {
|
||||
boost::system::error_code error;
|
||||
auto json = boost::json::parse(request.body(), error);
|
||||
if (error) {
|
||||
LOG(error) << "parse: [" << request.body() << "] failed, reason: " << error.message();
|
||||
return;
|
||||
}
|
||||
// LOG(debug) << "parse: [" << request.body() << "] succeed.";
|
||||
auto &req = json.as_object();
|
||||
MessageType type = MessageType::Text;
|
||||
if (req.contains("type")) {
|
||||
if (req.at("type").as_string() == "markdown") {
|
||||
type = MessageType::Markdown;
|
||||
}
|
||||
}
|
||||
if (req.contains("msg")) {
|
||||
std::string msg(req.at("msg").as_string());
|
||||
sendMessage(type, std::move(msg));
|
||||
}
|
||||
}
|
||||
|
||||
void Context::updateAccessToken() {
|
||||
boost::beast::error_code error;
|
||||
|
||||
boost::format target("/cgi-bin/gettoken?corpid=%1%&corpsecret=%2%");
|
||||
target % corpid % corpsecret;
|
||||
|
||||
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();
|
||||
int errcode = accessTokenObject.count("errcode") > 0 ? accessTokenObject.at("errcode").as_int64() : -1;
|
||||
if (errcode != 0) {
|
||||
LOG(error) << "get access_token failed,code: " << errcode << ", 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();
|
||||
});
|
||||
|
||||
static bool started = true;
|
||||
if (started) {
|
||||
sendMessage(MessageType::Text, "您好,艾玛已上线......");
|
||||
started = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Corporation
|
||||
} // namespace WeChat
|
49
WeChat/Corporation/Context.h
Normal file
49
WeChat/Corporation/Context.h
Normal file
@ -0,0 +1,49 @@
|
||||
#ifndef __CORPORATIONCONTEXT_H__
|
||||
#define __CORPORATIONCONTEXT_H__
|
||||
|
||||
#include "Core/Singleton.h"
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
|
||||
namespace WeChat {
|
||||
namespace Corporation {
|
||||
class Context : public std::enable_shared_from_this<Context> {
|
||||
friend class Core::Singleton<Context>;
|
||||
|
||||
public:
|
||||
enum MessageType {
|
||||
Text,
|
||||
Markdown,
|
||||
};
|
||||
using RequestType = boost::beast::http::request<boost::beast::http::string_body>;
|
||||
|
||||
void sendMessage(MessageType type, const std::string &message);
|
||||
void start();
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param request
|
||||
* @example curl -H "Content-Type: application/json" -X POST -d '{"user_id": "123", "msg":"OK!" }'
|
||||
* https://amass.fun/notify
|
||||
*/
|
||||
void notify(const RequestType &request);
|
||||
|
||||
protected:
|
||||
Context(boost::asio::io_context &ioContext);
|
||||
void updateAccessToken();
|
||||
|
||||
private:
|
||||
boost::asio::io_context &m_ioContext;
|
||||
boost::asio::steady_timer m_timer;
|
||||
std::string m_accessToken;
|
||||
|
||||
constexpr static auto host = "qyapi.weixin.qq.com";
|
||||
constexpr static auto port = "443";
|
||||
constexpr static auto corpid = "ww1a786851749bdadc";
|
||||
constexpr static auto corpsecret = "LlyJmYLIBOxJkQxkhwyqNVf550AUQ3JT2MT4yuS31i0";
|
||||
constexpr static auto agentid = 1000002;
|
||||
};
|
||||
} // namespace Corporation
|
||||
} // namespace WeChat
|
||||
#endif // __CORPORATIONCONTEXT_H__
|
183
WeChat/OfficialAccount/Context.cpp
Normal file
183
WeChat/OfficialAccount/Context.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
#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(m_ioContext, [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);
|
||||
});
|
||||
}
|
47
WeChat/OfficialAccount/Context.h
Normal file
47
WeChat/OfficialAccount/Context.h
Normal file
@ -0,0 +1,47 @@
|
||||
#ifndef WECHATCONTEXT_H
|
||||
#define WECHATCONTEXT_H
|
||||
|
||||
#include "Core/Singleton.h"
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
class WeChatSession;
|
||||
|
||||
class WeChatContext : public std::enable_shared_from_this<WeChatContext> {
|
||||
public:
|
||||
using OpenIds = std::vector<std::string>;
|
||||
|
||||
/**
|
||||
* @brief onWechat()函数调用了此函数,对接收到的消息进行处理
|
||||
*
|
||||
* @param body
|
||||
* @return std::string 返回给微信服务器
|
||||
*/
|
||||
std::string reply(const std::string &body);
|
||||
|
||||
protected:
|
||||
WeChatContext(boost::asio::io_context &ioContext);
|
||||
|
||||
void updateAccessToken();
|
||||
OpenIds users();
|
||||
std::string broadcast(const std::string_view &message);
|
||||
void cleanExpiredSessions(const boost::system::error_code &error = boost::system::error_code());
|
||||
|
||||
private:
|
||||
boost::asio::io_context &m_ioContext;
|
||||
boost::asio::steady_timer m_timer;
|
||||
std::string m_accessToken;
|
||||
|
||||
boost::asio::steady_timer m_sessionsExpireTimer;
|
||||
std::unordered_map<std::string, std::shared_ptr<WeChatSession>> m_sessions;
|
||||
|
||||
constexpr static std::chrono::seconds sessionExpireTime{5};
|
||||
constexpr static int httpVersion = 11;
|
||||
constexpr static auto host = "api.weixin.qq.com";
|
||||
constexpr static auto port = "443";
|
||||
constexpr static auto appid = "wxdb4253b5c4259708";
|
||||
constexpr static auto secret = "199780c4d3205d8b7b1f9be3382fbf82";
|
||||
};
|
||||
|
||||
#endif // WECHATCONTEXT_H
|
101
WeChat/OfficialAccount/Session.cpp
Normal file
101
WeChat/OfficialAccount/Session.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
#include "WeChatSession.h"
|
||||
#include "../ServiceManager.h"
|
||||
#include <BoostLog.h>
|
||||
#include <DateTime.h>
|
||||
|
||||
WeChatSession::WeChatSession(const std::string_view &username) : m_username(username) {
|
||||
m_lastAccessedTime = std::chrono::system_clock::now();
|
||||
initiate();
|
||||
}
|
||||
|
||||
std::string WeChatSession::processInput(const std::string_view &text) {
|
||||
ProcessInputEvent e;
|
||||
e.text = text;
|
||||
process_event(e);
|
||||
m_lastAccessedTime = std::chrono::system_clock::now();
|
||||
|
||||
std::string ret = std::move(m_reply);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void WeChatSession::printHelp() {
|
||||
std::ostringstream oss;
|
||||
oss << "1:设置闹钟" << std::endl;
|
||||
oss << "2:TTS" << std::endl;
|
||||
oss << "3:当前时间" << std::endl;
|
||||
oss << "4:随机播放音乐" << std::endl;
|
||||
oss << "5:停止播放音乐" << std::endl;
|
||||
oss << "<其它>:帮助" << std::endl;
|
||||
setReply(oss.str());
|
||||
}
|
||||
|
||||
void WeChatSession::printCurrentDateTime() {
|
||||
auto manager = Amass::Singleton<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage<CurrentDatetimeService>(CurrentDatetime);
|
||||
setReply("艾玛收到!将为您播报当前时间");
|
||||
}
|
||||
|
||||
void WeChatSession::playRandomMusic() {
|
||||
auto manager = Amass::Singleton<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage<PlayRandomMusicService>(PlayRandomMusic);
|
||||
setReply("艾玛收到!将为您随机播放音乐");
|
||||
}
|
||||
|
||||
void WeChatSession::stopPlayMusic() {
|
||||
auto manager = Amass::Singleton<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage(StopPlayMusic);
|
||||
setReply("艾玛收到!正在为您停止播放音乐");
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point WeChatSession::lastAccessedTime() const {
|
||||
return m_lastAccessedTime;
|
||||
}
|
||||
|
||||
void WeChatSession::setReply(std::string &&reply) {
|
||||
m_reply = std::move(reply);
|
||||
}
|
||||
|
||||
boost::statechart::result IdleState::react(const ProcessInputEvent &e) {
|
||||
auto &text = e.text;
|
||||
if (text == "1") {
|
||||
outermost_context().setReply("请输入闹钟时间:");
|
||||
return transit<SetAlarmState>();
|
||||
} else if (text == "2") {
|
||||
outermost_context().setReply("请输入TTS文字:");
|
||||
return transit<SetTtsState>();
|
||||
} else if (text == "3") {
|
||||
outermost_context().printCurrentDateTime();
|
||||
return discard_event();
|
||||
} else if (text == "4") {
|
||||
outermost_context().playRandomMusic();
|
||||
return discard_event();
|
||||
} else if (text == "5") {
|
||||
outermost_context().stopPlayMusic();
|
||||
return discard_event();
|
||||
} else {
|
||||
outermost_context().stopPlayMusic();
|
||||
outermost_context().printHelp();
|
||||
return discard_event();
|
||||
}
|
||||
}
|
||||
|
||||
boost::statechart::result SetAlarmState::react(const ProcessInputEvent &e) {
|
||||
auto &text = e.text;
|
||||
auto [hour, minute, second] = DateTime::parseTime(text);
|
||||
auto manager = Amass::Singleton<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage<SetAlarmClockService>(SetAlarmClock, hour, minute);
|
||||
std::ostringstream oss;
|
||||
oss << "set alarm clock at " << (int)hour << ":" << (int)minute;
|
||||
this->outermost_context().setReply(oss.str());
|
||||
return transit<IdleState>();
|
||||
}
|
||||
|
||||
SetAlarmState::SetAlarmState() {
|
||||
}
|
||||
|
||||
boost::statechart::result SetTtsState::react(const ProcessInputEvent &e) {
|
||||
auto manager = Amass::Singleton<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage(TextToSpeech, e.text);
|
||||
outermost_context().setReply(e.text.data());
|
||||
return transit<IdleState>();
|
||||
}
|
53
WeChat/OfficialAccount/Session.h
Normal file
53
WeChat/OfficialAccount/Session.h
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef __WECHATSESSION_H__
|
||||
#define __WECHATSESSION_H__
|
||||
|
||||
#include <boost/statechart/custom_reaction.hpp>
|
||||
#include <boost/statechart/simple_state.hpp>
|
||||
#include <boost/statechart/state_machine.hpp>
|
||||
#include <chrono>
|
||||
#include <string_view>
|
||||
|
||||
class ProcessInputEvent : public boost::statechart::event<ProcessInputEvent> {
|
||||
public:
|
||||
std::string text;
|
||||
};
|
||||
|
||||
class IdleState;
|
||||
|
||||
class WeChatSession : public boost::statechart::state_machine<WeChatSession, IdleState> {
|
||||
public:
|
||||
WeChatSession(const std::string_view &username);
|
||||
std::string processInput(const std::string_view &text);
|
||||
void printHelp();
|
||||
void printCurrentDateTime();
|
||||
void playRandomMusic();
|
||||
void stopPlayMusic();
|
||||
std::chrono::system_clock::time_point lastAccessedTime() const;
|
||||
void setReply(std::string &&reply);
|
||||
|
||||
private:
|
||||
std::string m_username;
|
||||
std::chrono::system_clock::time_point m_lastAccessedTime;
|
||||
std::string m_reply;
|
||||
};
|
||||
|
||||
class IdleState : public boost::statechart::simple_state<IdleState, WeChatSession> {
|
||||
public:
|
||||
typedef boost::statechart::custom_reaction<ProcessInputEvent> reactions;
|
||||
boost::statechart::result react(const ProcessInputEvent &);
|
||||
};
|
||||
|
||||
class SetAlarmState : public boost::statechart::simple_state<SetAlarmState, WeChatSession> {
|
||||
public:
|
||||
typedef boost::statechart::custom_reaction<ProcessInputEvent> reactions;
|
||||
boost::statechart::result react(const ProcessInputEvent &);
|
||||
SetAlarmState();
|
||||
};
|
||||
|
||||
class SetTtsState : public boost::statechart::simple_state<SetTtsState, WeChatSession> {
|
||||
public:
|
||||
typedef boost::statechart::custom_reaction<ProcessInputEvent> reactions;
|
||||
boost::statechart::result react(const ProcessInputEvent &);
|
||||
};
|
||||
|
||||
#endif // __WECHATSESSION_H__
|
Reference in New Issue
Block a user