一坨更大的垃圾
可以监听洛谷私信。
现在可以提供 windows 下的系统通知提示。
用 win32api 做出了图形化的界面。
可以发送 http 请求来发消息。
暂时不会发 release。因为我的 github 账号被 flagged 了,发了你们也看不到。想要 release 可以联系我 qq。
适用 win10/11 x64。
编译方法
请安装所需的库和头文件(openssl, websocketpp, boost, nlohmann/json.hpp, httplib)。
请对 C++ 代码使用编译 dos 脚本(compile.bat / compile.cmd):
g++.exe -c main.cpp -o main.o -I"c:/mingw64/include/c++/12.2.0" -I"c:/mingw64/include/c++/12.2.0/x86_64-w64-mingw32" -I"c:/mingw64/include/c++/12.2.0/backward" -I"c:/mingw64/lib/gcc/x86_64-w64-mingw32/12.2.0/include" -I"c:/mingw64/include" -I"c:/mingw64/lib/gcc/x86_64-w64-mingw32/12.2.0/include-fixed" -I"c:/mingw64/x86_64-w64-mingw32/include" -Wall -O2 -lws2_32 -lcrypt32 -lcryptui -lssl -lcrypto -finput-charset=utf-8 -fexec-charset=gbk
g++.exe main.o -o "main.exe" -L"c:/mingw64/lib/gcc/x86_64-w64-mingw32/12.2.0/" -L"c:/mingw64/lib/gcc/" -L"c:/mingw64/x86_64-w64-mingw32/lib/" -L"c:/mingw64/lib/" -L"C:/mingw64/lib" -L"C:/mingw64/x86_64-w64-mingw32/lib" -s -mwindows -lws2_32 -lcrypt32 -lcryptui -lssl -lcrypto
pause
我的编译器在 C:/mingw64 处,boost 之类所需的 lib 和 headers 等都混进了 mingw64 里。
请对 js 代码使用:pkg NotifierNodeJS.js --target=node18-win-x64,需要安装 NodeJS 18。
使用需求
请在 cpp 文件编译出的 exe 所在文件夹下额外放三个文件和一个文件夹:
- ISRG Root X1.crt(洛谷的证书文件)
- cookie.txt,填入你 cookie 里的 _uid 和 __client_id 部分,空格隔开
- NotifierNodeJS.exe,来源:js 代码打包而成
- "./notifier/snoretoast-x64.exe",来源:js 代码打包附带(实际上除了这个还有三个文件,那三个可以删)
如果需要分发,请额外包含:
- libcrypto-3-x64.dll
- libgcc_s_seh-1.dll
- libssl-3-x64.dll
- libstdc++-6.dll
- libwinpthread-1.dll
以及别忘了附上一个合适的 readme.txt 并删除 cookie.txt 中的内容。
源代码
为了缩短代码长度,删除了一些意义不大的内容和注释。
js
const notifier = require("node-notifier");
const opn = require("opn");
function notify(appName, title, content, icon, trigger) {
notifier.notify( { appName: appName, title: title, message: content, icon: icon, sound: true, wait: true }, (error, response, metadata) => {
if (!error) {
if (response == "activate" && metadata.activationType == "clicked") { if (trigger != "SP:NOTRIGGER") { opn(trigger); } }
else if (response == undefined) { if (trigger != "SP:NOTRIGGER") { opn(trigger); } }
}
}
);
}
const args = require('minimist')(process.argv.slice(2));
if ( args['appName'] != undefined && args['title'] != undefined && args['content'] != undefined && args['icon'] != undefined ) {
if (args['trigger'] == undefined) { notify (args['appName'], args['title'], args['content'], args['icon'], "SP:NOTRIGGER"); }
else { notify (args['appName'], args['title'], args['content'], args['icon'], args['trigger']); }
} else {
console.error("Invalid arguments.");
console.error("Usage: programName --appName=appName(string) --title=title(string) --content=content(string) --icon=icon(string: pathToFile / \"undefined\") [--trigger=trigger(string: app/link)]");
}
cpp
// Web headers: websocket, http
#include <websocketpp/config/asio_client.hpp>
#include <websocketpp/client.hpp>
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include <httplib.h>
// parsers
#include <nlohmann/json.hpp>
#include <regex>
// other headers
#include <iostream>
#include <wchar.h>
#include <ctime>
#include <cmath>
// Specfied: For a Windows application
#include <windows.h>
// Luogu APIs:
// https://github.com/0f-0b/luogu-api-docs
// https://0f-0b.github.io/luogu-api-docs/
typedef websocketpp::client<websocketpp::config::asio_tls_client> client;
typedef websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context> context_ptr;
client::connection_ptr* conptr; // sharing "con" in wsClientMain
HWND* hwndInputBox; // sharing "hwnd_ReceivedBox" in WndProc => DrawContents
int uid; // user UID of the client
std::string _uid; // cookie value of _uid of the client (string)
std::string __client_id; // cookie value of __client_id of the client
std::string cookie; // full cookie of the client
httplib::Client cliFiles("https://cdn.luogu.com.cn");
httplib::Client cliMsg("https://www.luogu.com.cn");
const std::string cert = "ISRG Root X1.crt";
// For alive timing (invalid connection checkers)
clock_t time_req, timePing_req;
bool islogon = 0;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
namespace fs = std::filesystem;
/// @brief Check the existence of file, if file not exists then create one.
/// @param filePath Full (relative / absolute) path of the file.
/// @param initContent Initial content of the file (if need to create one)
/// @return the ifstream of the existing/created file
std::ifstream initFile(const std::string& filePath, const std::string& initContent) {
// json parser wil not work without a existed json file and the program will terminate.
fs::path directory = fs::path(filePath).parent_path();
if (!fs::exists(directory)) {fs::create_directories(directory);} // creath directory
std::ofstream file(filePath);
if (file.is_open()) {
file << initContent; // init file
file.close();
}
return std::ifstream(filePath);
}
namespace TLS_INIT {
// This namespace is derived from examples and documentations.
// https://github.com/zaphoyd/websocketpp/issues/706
// https://github.com/cotomonaga/websocketpp_tutorial/blob/master/utility_client/step7.cpp
// https://blog.csdn.net/byxdaz/article/details/84645586
bool verify_subject_alternative_name(const char* hostname, X509* cert) {
STACK_OF(GENERAL_NAME)* san_names = NULL;
san_names = (STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
if (san_names == NULL) {
return false;
}
int san_names_count = sk_GENERAL_NAME_num(san_names);
bool result = false;
for (int i = 0; i < san_names_count; i++) {
const GENERAL_NAME* current_name = sk_GENERAL_NAME_value(san_names, i);
if (current_name->type != GEN_DNS) {
continue;
}
char const* dns_name = (char const*)ASN1_STRING_get0_data(current_name->d.dNSName);
if (ASN1_STRING_length(current_name->d.dNSName) != (int) strlen(dns_name)) {
break;
}
result = (strcasecmp(hostname, dns_name) == 0);
}
sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
return result;
}
bool verify_common_name(char const* hostname, X509* cert) {
int common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name(cert), NID_commonName, -1);
if (common_name_loc < 0) {
return false;
}
X509_NAME_ENTRY* common_name_entry = X509_NAME_get_entry(X509_get_subject_name(cert), common_name_loc);
if (common_name_entry == NULL) {
return false;
}
ASN1_STRING* common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
if (common_name_asn1 == NULL) {
return false;
}
char const* common_name_str = (char const*)ASN1_STRING_get0_data(common_name_asn1);
if (ASN1_STRING_length(common_name_asn1) != (int) strlen(common_name_str)) {
return false;
}
return (strcasecmp(hostname, common_name_str) == 0);
}
bool verify_certificate(const char* hostname, bool preverified, boost::asio::ssl::verify_context& ctx) {
int depth = X509_STORE_CTX_get_error_depth(ctx.native_handle( ));
if (depth == 0 && preverified) {
X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle( ));
if (verify_subject_alternative_name(hostname, cert)) {
return true;
} else if (verify_common_name(hostname, cert)) {
return true;
} else {
return false;
}
}
return preverified;
}
context_ptr on_tls_init(const char* hostname, websocketpp::connection_hdl) {
context_ptr ctx = websocketpp::lib::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23);
try {
ctx->set_options(boost::asio::ssl::context::default_workarounds |
boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::no_sslv3 |
boost::asio::ssl::context::single_dh_use);
ctx->set_verify_mode(boost::asio::ssl::verify_peer);
ctx->set_verify_callback(bind(&verify_certificate, hostname, ::_1, ::_2));
ctx->load_verify_file(cert.c_str( ));
} catch (std::exception& e) {
std::cout << e.what( ) << std::endl;
}
return ctx;
}
}
using namespace TLS_INIT;
// For "drawContentInputBox".
std::string msgConnected = ""; // the connected string of received messages
// std::string msgArchive[65536]; // the string archive of received messages
// int msgCount = 0; // the number of received messages
/// @brief Draw text in a input box (specifically for message receive box)
/// @param addstr the text needed to add
void drawContentInputBox (std::string addstr) {
// msgArchive[msgCount] = addstr; // archive the message (in fact not necessary)
msgConnected += addstr + "\r\n\r\n"; // connect the message
// msgCount += 1; // for archiving
SetWindowText((*hwndInputBox), msgConnected.c_str()); // print
}
/// @brief Function used to close connection
/// @param reason Reason to close connection that put into the input box.
void closeConnection (std::string reason) {
drawContentInputBox (reason);
websocketpp::lib::error_code ec;
// std::string reason is NOT for the reason of closing connection here
(*conptr)->close(websocketpp::close::status::going_away, "", ec);
if (ec) {
std::string errInfo = "Error closing connection: " + ec.message();
drawContentInputBox (errInfo);
MessageBox(NULL, errInfo.c_str(), TEXT("Error"), MB_OK | MB_ICONERROR);
}
}
// Invalid connection checkers.
namespace checkerThreadFuncs {
/// @brief Check if the client is logged in.
void loginChecker ( ) { /*
If the client have no "join_result" after the program start for 5 secs,
the client may have a invalid cookie.
*/ Sleep (1000 * 5);
if (islogon == 0) {
closeConnection ("Connection will restart, for join_result is not received in 5 seconds. "
"Please fill your cookies (_uid, __client_id) correctly. " + _uid + " " + __client_id);
}
}
/// @brief Check if the client is disconnected by ping.
void pingTimer ( ) { /*
If the client received no ping after the time interval of 60 secs,
the client may be automaticly disconnected, maybe for logged in on a different client.
Normally, the ping interval is 54 secs, the range of checking is 60 secs.
*/ Sleep (1000 * 60);
if ((float) (clock ( ) - timePing_req) / CLOCKS_PER_SEC > 60.0) {
closeConnection ("Connection will restart, for no ping is receiced for 1 minute "
"(normal: approx 54 seconds).");
}
}
/// @brief Check if the client is disconnected by heartbeat.
void heartbeatTimer ( ) { /*
If the client received no heartbeat after the time interval of 96 secs,
the client may be automaticly disconnected, maybe for logged in on a different client.
Normally, the ping interval is 90 secs, the range of checking is 96 secs.
*/ Sleep (1000 * 96);
if ((float) (clock ( ) - time_req) / CLOCKS_PER_SEC > 96.0) {
closeConnection ("Connection will restart, for no heartbeat is receiced for 1.6 minutes "
"(normal: approx 90 seconds).");
}
}
}
using namespace checkerThreadFuncs;
/// @brief Turn UTF-8 string to GBK string. (-finput-charset=utf-8 -fexec-charset=gbk)
/// @param utf8Str The UTF-8 string
/// @return The GBK format string
std::string UTF8ToGBK (const std::string& utf8Str) {
// The program is targeted to a Chinese website, so there are encoding problems
int utf8Size = MultiByteToWideChar (CP_UTF8, 0, utf8Str.c_str ( ), -1, NULL, 0);
std::wstring utf16Str;
utf16Str.resize (utf8Size - 1);
MultiByteToWideChar (CP_UTF8, 0, utf8Str.c_str ( ), -1, &utf16Str[0], utf8Size);
int gbkSize = WideCharToMultiByte (CP_ACP, 0, utf16Str.c_str ( ), -1, NULL, 0, NULL, NULL);
std::string gbkStr;
gbkStr.resize (gbkSize - 1);
WideCharToMultiByte (CP_ACP, 0, utf16Str.c_str ( ), -1, &gbkStr[0], gbkSize, NULL, NULL);
return gbkStr;
}
/// @brief Turn GBK string to UTF-8 string. (-finput-charset=utf-8 -fexec-charset=gbk)
/// @param gbkStr The GBK string
/// @return The UTF-8 format string
std::string GBKToUTF8(const std::string& gbkStr) {
// The program is targeted to a Chinese website, so there are encoding problems
int utf16Size = MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), -1, NULL, 0);
std::wstring utf16Str;
utf16Str.resize(utf16Size - 1);
MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), -1, &utf16Str[0], utf16Size);
int utf8Size = WideCharToMultiByte(CP_UTF8, 0, utf16Str.c_str(), -1, NULL, 0, NULL, NULL);
std::string utf8Str;
utf8Str.resize(utf8Size - 1);
WideCharToMultiByte(CP_UTF8, 0, utf16Str.c_str(), -1, &utf8Str[0], utf8Size, NULL, NULL);
return utf8Str;
}
/// @brief Use notifier written out of NodeJS to send Win10/11 desktop notifications of Luogu messages.
/// @param Message Content of the message to display
/// @param Sender Name of the sender to display
/// @param SenderUID UID of the sender, for displaying the avatar
void NotifyNodeJS (std::string Message, std::string Sender, std::string SenderUID) {
// Generate the command
std::string appNameParam = "--appName=\"Luogu Message Notifier\" ";
std::string titleParam = "--title=\"A message from " + Sender + "\" ";
std::string contentParam = "--content=\"" + Message + "\" ";
std::string iconParam = "--icon=\"./cache/" + SenderUID + ".png\" ";
std::string triggerParam = "--trigger=\"https://www.luogu.com.cn/chat?uid=" + SenderUID + "\" ";
std::string command = "\"./NotifierNodeJS.exe\" " + appNameParam
+ titleParam + contentParam + iconParam + triggerParam;
// Silent execute (not creating an extra flashing cmd window)
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory (&si, sizeof (si));
si.cb = sizeof (si);
ZeroMemory (&pi, sizeof (pi));
char cmdProcessed[command.length ( ) + 1];
strcpy (cmdProcessed, command.c_str ( ));
CreateProcess (NULL, cmdProcessed, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
CloseHandle (pi.hProcess);
CloseHandle (pi.hThread);
}
/// @brief Get the avatar of the user
/// @param userid Luogu user uid
void getAvatar (std::string userid) {
// Read or generate the cache.json
std::ifstream in = initFile("./cache/cache.json", "{}");
nlohmann::json infile = nlohmann::json::parse(in);
in.close( );
// Check if the avatar exists and is valid. Avatar cached for 1 hour
if (!infile[userid].is_null( )) {
int usrID = infile[userid];
if ((int)time(0) - usrID < 3600) {
return;
}
}
// Use HTTP GET method to get the avatar. Luogu APIs
std::string req = "/upload/usericon/" + userid + ".png";
if (auto res = cliFiles.Get(req)) {
if (res->status == 200) {
std::string outpic = "./cache/" + userid + ".png";
std::ofstream outfile(outpic.c_str( ), std::ofstream::binary);
outfile.write(res->body.c_str( ), res->body.size( ));
outfile.close( );
// std::cout << " Image downloaded successfully.\n";
infile[userid] = time(0);
std::ofstream out("./cache/cache.json");
out << std::setw(4) << infile << std::endl;
out.close( );
} else {
std::string errInfo = "Failed to get avatar: HTTP STATUS ";
errInfo += std::to_string((int) res->status);
MessageBox(NULL, errInfo.c_str(), TEXT("Error"), MB_OK | MB_ICONERROR);
}
} else {
auto err = res.error();
std::string errInfo = "HTTP error: ";
errInfo += httplib::to_string(err);
MessageBox(NULL, errInfo.c_str(), TEXT("Error"), MB_OK | MB_ICONERROR);
}
}
/// @brief Actions when receiving a ping.
/// @param hdl
/// @param payload
/// @return
bool on_ping(websocketpp::connection_hdl hdl, std::string payload) {
timePing_req = clock ( );
std::thread thrPing (pingTimer);
thrPing.detach ( );
return true;
}
void on_message(websocketpp::connection_hdl hdl, client::message_ptr msg) {
std::string message = msg->get_payload( );
nlohmann::json u = nlohmann::json::parse(message);
if (u["_ws_type"] == "server_broadcast" && u["message"]["receiver"]["uid"] == uid) {
// Message from user
getAvatar (std::to_string((int) u["message"]["sender"]["uid"]));
std::string sender = u["message"]["sender"]["name"];
std::string content = u["message"]["content"];
// Convert format
sender = UTF8ToGBK (sender);
content = UTF8ToGBK (content);
// Desktop notification
std::thread Notif(bind(NotifyNodeJS, content, sender,
std::to_string((int) u["message"]["sender"]["uid"])));
Notif.detach ( );
// Write in input box
drawContentInputBox (sender + " > " + content);
} else if (u["_ws_type"] == "heartbeat") {
// Heartbeat
time_req = clock ( );
std::thread thrHeartbeat (heartbeatTimer);
thrHeartbeat.detach ( );
} else if (u["_ws_type"] == "join_result") {
// Logging in with correct cookie
islogon = 1;
drawContentInputBox ("Login successfully!");
time_req = clock ( );
std::thread thrHeartbeat (heartbeatTimer);
thrHeartbeat.detach ( );
}
}
signed wsClientMain( ) {
// Main part of the websocket client.
// the former "main" function before the WinMain update
client::connection_ptr con;
conptr = &con; // share
client c;
std::string uri = "wss://ws.luogu.com.cn/ws";
std::string tls_init_handlr = "ws.luogu.com.cn";
// Init cookies -> Luogu APIs
std::ifstream inCookie("cookie.txt");
inCookie >> _uid >> __client_id;
inCookie.close ( );
int ulen = _uid.length ( );
uid = 0;
for (int i = 0; i < ulen; i++) uid = uid * 10 + _uid[i] - '0';
cookie = "__client_id=" + __client_id + "; _uid=" + _uid;
cliFiles.set_default_headers ({ { "Cookie", cookie } });
cliMsg.set_default_headers ({
{ "Cookie", cookie },
{ "referer", "https://www.luogu.com.cn/" }
});
// Init join message -> Luogu APIs
nlohmann::json joinMessage = {
{"type", "join_channel"},
{"channel", "chat"},
{"channel_param", _uid},
{"exclusive_key", nullptr}
};
std::string messageStr = joinMessage.dump ( );
try {
// channels
c.set_access_channels (websocketpp::log::alevel::all);
c.clear_access_channels (websocketpp::log::alevel::frame_payload);
c.set_error_channels (websocketpp::log::elevel::all);
c.init_asio ( );
// handlers
c.set_message_handler (bind (&on_message, ::_1, ::_2));
c.set_ping_handler (bind (&on_ping, ::_1, ::_2));
c.set_tls_init_handler (bind (&on_tls_init, tls_init_handlr.c_str ( ), ::_1));
c.set_open_handler ([messageStr, &c](websocketpp::connection_hdl hdl) {
drawContentInputBox ("Connection opened!");
c.send(hdl, messageStr, websocketpp::frame::opcode::text);
timePing_req = clock ( );
// Connection opened, start timing for join result and ping
std::thread thrLogin (loginChecker);
thrLogin.detach ( );
std::thread thrPing (pingTimer);
thrPing.detach ( );
});
websocketpp::lib::error_code ec;
con = c.get_connection(uri, ec);
if (ec) {
std::string prefConnectionFailed = "Could not create connection because: ";
std::string reasonConnectionFailed = ec.message( );
prefConnectionFailed += reasonConnectionFailed;
drawContentInputBox (prefConnectionFailed.c_str( ));
}
con->append_header ("Cookie", cookie);
c.connect (con);
c.get_alog ( ).write (websocketpp::log::alevel::app, "Connecting to " + uri);
c.run ( );
// After connection terminated
c.stop ( );
c.reset ( );
con.reset ( );
} catch (websocketpp::exception const& e) {
std::string prefException = "Caught exception: ";
std::string msgException = e.what ( );
prefException += msgException;
drawContentInputBox (prefException.c_str ( ));
MessageBox(NULL, e.what( ), TEXT("Error"), MB_OK | MB_ICONERROR);
}
return 0;
}
void wsClientProt ( ) {
bool isConnected = true;
while (isConnected == true) {
wsClientMain ( );
drawContentInputBox("Connection refreshing... cooldown 5s");
Sleep (5000);
}
}
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
WNDCLASSEX wc;
HWND hwnd;
MSG msg;
memset (&wc, 0, sizeof (wc));
wc.cbSize = sizeof (WNDCLASSEX);
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = TEXT ("WindowClass");
wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wc.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
if (!RegisterClassEx (&wc)) {
MessageBox (NULL, TEXT ("Window Registration Failed!"), TEXT ("Error"), MB_ICONEXCLAMATION | MB_OK);
return 0;
}
HWND hwndDT = GetDesktopWindow ( );
RECT rect;
GetWindowRect (hwndDT, &rect);
int dtWidth = rect.right - rect.left;
int dtHeight = rect.bottom - rect.top;
hwnd = CreateWindowEx (WS_EX_CLIENTEDGE, wc.lpszClassName, TEXT ("FALCON A Luogu Chat On-desktop Notifier"), WS_VISIBLE | WS_OVERLAPPEDWINDOW, dtWidth * 1 / 8, dtHeight * 1 / 8, dtWidth * 3 / 4, dtHeight * 3 / 4, NULL, NULL, hInstance, NULL);
if (hwnd == NULL) {
MessageBox(NULL, TEXT ("Window Creation Failed!"), TEXT ("Error"), MB_ICONEXCLAMATION | MB_OK);
return 0;
}
while (GetMessage (&msg, NULL, 0, 0) > 0) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {
static HWND hwnd_Button;
static HWND hwnd_msg_Editbox;
static HWND hwnd_UID_Editbox;
static HWND hwnd_ReceivedBox;
char buff[4096] = {0};
char uid_buff[64] = {0};
const int ID_cmdButton = 1;
const int ID_msg_EditBox = 2;
const int ID_UID_EditBox = 3;
const int ID_ReceivedBox = 4;
RECT wRect;
int wWidth, wHeight;
#define INPUT_HEIGHT (wHeight * 0.17 < 108 ? 108 : wHeight * 0.17)
#define BUTTON_HEIGHT (wHeight * 0.13 < 80 ? 80 : wHeight * 0.13)
#define UID_HEIGHT (wHeight * 0.04 < 28 ? 28 : wHeight * 0.04)
auto DrawContents = [&] ( ) {
GetClientRect(hwnd, &wRect);
wWidth = wRect.right - wRect.left;
wHeight = wRect.bottom - wRect.top;
hwnd_Button = CreateWindow (TEXT ("button"), TEXT ("Send!"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, wWidth * 0.78, wHeight - BUTTON_HEIGHT, wWidth * 0.22, BUTTON_HEIGHT, hwnd, reinterpret_cast <HMENU> (ID_cmdButton), ((LPCREATESTRUCT) lParam)->hInstance, NULL);
if (!hwnd_Button) MessageBox(NULL, TEXT ("Failed to create button!"), TEXT ("Message"), MB_OK | MB_ICONERROR);
hwnd_UID_Editbox = CreateWindow (TEXT ("edit"), NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, wWidth * 0.78, wHeight - INPUT_HEIGHT, wWidth * 0.22, UID_HEIGHT, hwnd, reinterpret_cast <HMENU> (ID_UID_EditBox), NULL, NULL);
if (!hwnd_UID_Editbox) MessageBox(NULL, TEXT ("Failed to create uid input box!"), TEXT ("Message"), MB_OK | MB_ICONERROR);
hwnd_msg_Editbox = CreateWindow (TEXT ("edit"), NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL, wWidth * 0, wHeight - INPUT_HEIGHT, wWidth * 0.78, INPUT_HEIGHT, hwnd, reinterpret_cast <HMENU> (ID_msg_EditBox), NULL, NULL);
if (!hwnd_msg_Editbox) MessageBox(NULL, TEXT ("Failed to create message input box!"), TEXT ("Message"), MB_OK | MB_ICONERROR);
hwnd_ReceivedBox = CreateWindow (TEXT ("edit"), TEXT (""), WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL, 1 + wWidth * 0, wHeight * 0 + 1, wWidth * 1 - 1, wHeight - INPUT_HEIGHT - 1, hwnd, reinterpret_cast <HMENU> (ID_ReceivedBox), NULL, NULL);
if (!hwnd_ReceivedBox) MessageBox(NULL, TEXT ("Failed to create message receive box!"), TEXT ("Message"), MB_OK | MB_ICONERROR);
hwndInputBox = &hwnd_ReceivedBox; // share
};
auto Resize = [&] ( ) {
GetClientRect(hwnd, &wRect);
wWidth = wRect.right - wRect.left;
wHeight = wRect.bottom - wRect.top;
SetWindowPos (hwnd_Button, NULL, wWidth * 0.78, wHeight - BUTTON_HEIGHT, wWidth * 0.22, BUTTON_HEIGHT, SWP_NOZORDER);
SetWindowPos (hwnd_msg_Editbox, NULL, wWidth * 0, wHeight - INPUT_HEIGHT, wWidth * 0.78, INPUT_HEIGHT, SWP_NOZORDER);
SetWindowPos (hwnd_UID_Editbox, NULL, wWidth * 0.78, wHeight - INPUT_HEIGHT, wWidth * 0.22, UID_HEIGHT, SWP_NOZORDER);
SetWindowPos (hwnd_ReceivedBox, NULL, 1 + wWidth * 0, wHeight * 0 + 1, wWidth * 1 - 1, wHeight - INPUT_HEIGHT - 1, SWP_NOZORDER);
};
auto extract_csrf_token = [] (std::string html) {
std::regex csrf_regex("meta name=\"csrf-token\" content=\"(.*?)\"");
std::smatch match;
if (std::regex_search(html, match, csrf_regex)) {
return (std::string) match[1];
} else {
return (std::string) "";
}
};
switch (Message) {
case WM_COMMAND: {
switch (LOWORD (wParam)) {
case 0:
PostQuitMessage (0); break;
case ID_cmdButton:
GetWindowText (hwnd_msg_Editbox, buff, 4096);
GetWindowText (hwnd_UID_Editbox, uid_buff, 64);
if (buff[0] && uid_buff[0]) {
std::string sbuff = buff;
std::string suidbuff = uid_buff;
std::string respLnk = "/api/user/search?keyword=" + suidbuff;
std::string resp = cliMsg.Get (respLnk)->body;
nlohmann::json respJson = nlohmann::json::parse(resp);
int usr_uid = respJson["users"][0]["uid"];
std::string usr_name = UTF8ToGBK((std::string)respJson["users"][0]["name"]);
std::string csrf_token = extract_csrf_token (cliMsg.Get ("/")->body);
httplib::Headers addHeaders = { { "x-csrf-token", csrf_token } };
std::string cont = GBKToUTF8(sbuff);
nlohmann::json body = { { "user", usr_uid }, { "content", cont } };
auto res = cliMsg.Post("/api/chat/new", addHeaders, body.dump ( ), "application/json");
if (res) {
if (res->status == 200) {
drawContentInputBox ("Request succeeded in sending message. You have sent to " +
usr_name + ": " + sbuff);
} else {
drawContentInputBox ("Request failed in sending message. Status code: " + std::to_string(res->status));
}
} else {
drawContentInputBox ("Request failed in sending message. Error code: " + std::to_string((int)res.error()));
}
SetWindowText (hwnd_msg_Editbox, TEXT (""));
SetWindowText (hwnd_UID_Editbox, TEXT (""));
} else {
MessageBox(NULL, TEXT ("uid not filled"), TEXT ("Error"), MB_ICONASTERISK);
}
break;
}
break;
}
case WM_CREATE: {
HFONT hFont = CreateFont(19, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,DEFAULT_PITCH | FF_DONTCARE, "Microsoft YaHei UI");
SendMessage(hwnd, WM_SETFONT, (WPARAM)hFont, TRUE);
DrawContents();
SendMessage(hwnd_Button, WM_SETFONT, (WPARAM)hFont, TRUE);
SendMessage(hwnd_msg_Editbox, WM_SETFONT, (WPARAM)hFont, TRUE);
SendMessage(hwnd_UID_Editbox, WM_SETFONT, (WPARAM)hFont, TRUE);
SendMessage(hwnd_ReceivedBox, WM_SETFONT, (WPARAM)hFont, TRUE);
std::thread thrWsClient(wsClientProt);
thrWsClient.detach ( );
break;
}
case WM_GETMINMAXINFO: {
MINMAXINFO* lpMinMaxInfo = (MINMAXINFO*)lParam;
lpMinMaxInfo->ptMinTrackSize.x = 600;
lpMinMaxInfo->ptMinTrackSize.y = 400;
break;
}
case WM_SIZE: {
Resize(); break;
}
case WM_CLOSE: {
DestroyWindow(hwnd); break;
}
case WM_DESTROY: {
closeConnection("Connection closed because program shut down"); PostQuitMessage(0); break;
}
default: {
return DefWindowProc(hwnd, Message, wParam, lParam);
}
}
return 0;
}