summaryrefslogtreecommitdiff
path: root/protocols/MinecraftDynmap/src/communication.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/MinecraftDynmap/src/communication.cpp')
-rw-r--r--protocols/MinecraftDynmap/src/communication.cpp465
1 files changed, 465 insertions, 0 deletions
diff --git a/protocols/MinecraftDynmap/src/communication.cpp b/protocols/MinecraftDynmap/src/communication.cpp
new file mode 100644
index 0000000000..98b0728483
--- /dev/null
+++ b/protocols/MinecraftDynmap/src/communication.cpp
@@ -0,0 +1,465 @@
+/*
+
+Minecraft Dynmap plugin for Miranda Instant Messenger
+_____________________________________________
+
+Copyright © 2015 Robert Pösel
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+http::response MinecraftDynmapProto::sendRequest(const int request_type, std::string *post_data, std::string *get_data)
+{
+ http::response resp;
+
+ // Prepare the request
+ NETLIBHTTPREQUEST nlhr = { sizeof(NETLIBHTTPREQUEST) };
+
+ // FIXME: get server
+
+ // Set request URL
+ std::string url = m_server + chooseAction(request_type, get_data);
+ nlhr.szUrl = (char*)url.c_str();
+
+ // Set timeout (bigger for channel request)
+ nlhr.timeout = 1000 * ((request_type == MINECRAFTDYNMAP_REQUEST_EVENTS) ? 65 : 20);
+
+ // Set request type (GET/POST) and eventually also POST data
+ if (post_data != NULL) {
+ nlhr.requestType = REQUEST_POST;
+ nlhr.pData = (char*)(*post_data).c_str();
+ nlhr.dataLength = (int)post_data->length();
+ }
+ else {
+ nlhr.requestType = REQUEST_GET;
+ }
+
+ // Set headers - it depends on requestType so it must be after setting that
+ nlhr.headers = get_request_headers(nlhr.requestType, &nlhr.headersCount);
+
+ // Set flags
+ nlhr.flags = NLHRF_HTTP11;
+
+#ifdef _DEBUG
+ nlhr.flags |= NLHRF_DUMPASTEXT;
+#else
+ nlhr.flags |= NLHRF_NODUMP;
+#endif
+
+ // Set persistent connection (or not)
+ switch (request_type)
+ {
+ case MINECRAFTDYNMAP_REQUEST_HOME:
+ nlhr.nlc = NULL;
+ break;
+
+ case MINECRAFTDYNMAP_REQUEST_EVENTS:
+ nlhr.nlc = hEventsConnection;
+ nlhr.flags |= NLHRF_PERSISTENT;
+ break;
+
+ default:
+ WaitForSingleObject(connection_lock_, INFINITE);
+ nlhr.nlc = hConnection;
+ nlhr.flags |= NLHRF_PERSISTENT;
+ break;
+ }
+
+ debugLogA("@@@@@ Sending request to '%s'", nlhr.szUrl);
+
+ // Send the request
+ NETLIBHTTPREQUEST *pnlhr = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)m_hNetlibUser, (LPARAM)&nlhr);
+
+ mir_free(nlhr.headers);
+
+ // Remember the persistent connection handle (or not)
+ switch (request_type)
+ {
+ case MINECRAFTDYNMAP_REQUEST_HOME:
+ break;
+
+ case MINECRAFTDYNMAP_REQUEST_EVENTS:
+ hEventsConnection = pnlhr ? pnlhr->nlc : NULL;
+ break;
+
+ default:
+ ReleaseMutex(connection_lock_);
+ hConnection = pnlhr ? pnlhr->nlc : NULL;
+ break;
+ }
+
+ // Check and copy response data
+ if (pnlhr != NULL)
+ {
+ debugLogA("@@@@@ Got response with code %d", pnlhr->resultCode);
+ store_headers(&resp, pnlhr->headers, pnlhr->headersCount);
+ resp.code = pnlhr->resultCode;
+ resp.data = pnlhr->pData ? pnlhr->pData : "";
+
+ // debugLogA("&&&&& Got response: %s", resp.data.c_str());
+
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)pnlhr);
+ } else {
+ debugLogA("!!!!! No response from server (time-out)");
+ resp.code = HTTP_CODE_FAKE_DISCONNECTED;
+ // Better to have something set explicitely as this value is compaired in all communication requests
+ }
+
+ return resp;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+std::string MinecraftDynmapProto::chooseAction(int request_type, std::string *get_data)
+{
+ switch (request_type) {
+ case MINECRAFTDYNMAP_REQUEST_MESSAGE: {
+ return "/up/sendmessage";
+ }
+
+ case MINECRAFTDYNMAP_REQUEST_CONFIGURATION: {
+ return "/up/configuration";
+ }
+
+ case MINECRAFTDYNMAP_REQUEST_EVENTS: {
+ std::string request = "/up/world/%s/%s";
+
+ // Set world
+ std::string world = "world"; // TODO: configurable world?
+ utils::text::replace_first(&request, "%s", world);
+
+ // Set timestamp
+ utils::text::replace_first(&request, "%s", !m_timestamp.empty() ? m_timestamp : "0");
+
+ return request;
+ }
+
+ //case MINECRAFTDYNMAP_REQUEST_HOME:
+ default: {
+ return "/" + *get_data;
+ }
+ }
+}
+
+
+NETLIBHTTPHEADER* MinecraftDynmapProto::get_request_headers(int request_type, int* headers_count)
+{
+ if (request_type == REQUEST_POST)
+ *headers_count = 5;
+ else
+ *headers_count = 4;
+
+ NETLIBHTTPHEADER *headers = (NETLIBHTTPHEADER*)mir_calloc(sizeof(NETLIBHTTPHEADER)*(*headers_count));
+
+ if (request_type == REQUEST_POST) {
+ headers[4].szName = "Content-Type";
+ headers[4].szValue = "application/json; charset=utf-8";
+ }
+
+ headers[3].szName = "Cookie";
+ headers[3].szValue = (char *)m_cookie.c_str();
+ headers[2].szName = "User-Agent";
+ headers[2].szValue = (char *)g_strUserAgent.c_str();
+ headers[1].szName = "Accept";
+ headers[1].szValue = "*/*";
+ headers[0].szName = "Accept-Language";
+ headers[0].szValue = "en,en-US;q=0.9";
+
+ return headers;
+}
+
+void MinecraftDynmapProto::store_headers(http::response* resp, NETLIBHTTPHEADER* headers, int headersCount)
+{
+ for (size_t i = 0; i < (size_t)headersCount; i++) {
+ std::string header_name = headers[i].szName;
+ std::string header_value = headers[i].szValue;
+
+ resp->headers[header_name] = header_value;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+bool MinecraftDynmapProto::doSignOn()
+{
+ handleEntry(__FUNCTION__);
+
+ http::response resp = sendRequest(MINECRAFTDYNMAP_REQUEST_CONFIGURATION);
+
+ if (resp.code != HTTP_CODE_OK) {
+ return handleError(__FUNCTION__, "Can't load configuration", true);
+ }
+
+ JSONROOT root(resp.data.c_str());
+ if (root == NULL)
+ return false;
+
+ /*
+ JSONNODE *allowchat_ = json_get(root, "allowchat"); // boolean
+ JSONNODE *allowwebchat_ = json_get(root, "allowwebchat"); // boolean
+ JSONNODE *loggedin_ = json_get(root, "loggedin"); // boolean
+ JSONNODE *loginEnabled_ = json_get(root, "login-enabled"); // boolean
+ JSONNODE *loginRequired_ = json_get(root, "webchat-requires-login"); // boolean
+ */
+
+ JSONNODE *title_ = json_get(root, "title"); // name of server
+ JSONNODE *interval_ = json_get(root, "webchat-interval"); // limit in seconds for sending messages
+ JSONNODE *rate_ = json_get(root, "updaterate"); // probably update rate for events request
+
+ if (title_ == NULL || interval_ == NULL || rate_ == NULL) {
+ return handleError(__FUNCTION__, "No title, interval or rate in configuration", true);
+ }
+
+ m_title = json_as_pstring(title_);
+ m_interval = json_as_int(interval_);
+ m_updateRate = json_as_int(rate_);
+ m_cookie.clear();
+
+ if (resp.headers.find("Set-Cookie") != resp.headers.end()) {
+ // Load Session identifier
+ std::string cookies = resp.headers["Set-Cookie"];
+
+ const char *findStr = "JSESSIONID=";
+ std::string::size_type start = cookies.find(findStr);
+
+ if (start != std::string::npos) {
+ m_cookie = cookies.substr(start, cookies.find(";") - start);
+ }
+ }
+
+ if (m_cookie.empty()) {
+ return handleError(__FUNCTION__, "Empty session id", true);
+ }
+
+ return handleSuccess(__FUNCTION__);
+}
+
+bool MinecraftDynmapProto::doEvents()
+{
+ handleEntry(__FUNCTION__);
+
+ // Get update
+ http::response resp = sendRequest(MINECRAFTDYNMAP_REQUEST_EVENTS);
+
+ if (resp.code != HTTP_CODE_OK)
+ return handleError(__FUNCTION__, "Response is not code 200");
+
+ JSONROOT root(resp.data.c_str());
+ if (root == NULL)
+ return handleError(__FUNCTION__, "Invalid JSON response");
+
+ JSONNODE *timestamp_ = json_get(root, "timestamp");
+ if (timestamp_ == NULL)
+ return handleError(__FUNCTION__, "Received no timestamp node");
+
+ m_timestamp = json_as_pstring(timestamp_);
+
+ JSONNODE *updates_ = json_get(root, "updates");
+ if (updates_ == NULL)
+ return handleError(__FUNCTION__, "Received no updates node");
+
+ for (unsigned int i = 0; i < json_size(updates_); i++) {
+ JSONNODE *it = json_at(updates_, i);
+
+ JSONNODE *type_ = json_get(it, "type");
+ if (type_ != NULL && json_as_pstring(type_) == "chat") {
+ JSONNODE *time_ = json_get(it, "timestamp");
+ // JSONNODE *source_ = json_get(it, "source"); // e.g. "web"
+ JSONNODE *playerName_ = json_get(it, "playerName");
+ JSONNODE *message_ = json_get(it, "message");
+ // TODO: there are also "channel" and "account" elements
+
+ if (time_ == NULL || playerName_ == NULL || message_ == NULL) {
+ debugLog(_T("Error: No player name, time or text for message"));
+ continue;
+ }
+
+ time_t timestamp = utils::time::from_string(json_as_pstring(time_));
+ ptrT name(json_as_string(playerName_));
+ ptrT message(json_as_string(message_));
+
+ debugLog(_T("Received message: [%d] %s -> %s"), timestamp, name, message);
+ UpdateChat(name, message, timestamp);
+ }
+ }
+
+ return handleSuccess(__FUNCTION__);
+}
+
+bool MinecraftDynmapProto::doSendMessage(const std::string &message_text)
+{
+ handleEntry(__FUNCTION__);
+
+ std::string data = "{\"name\":\"";
+ data += this->nick_;
+ data += "\", \"message\" : \"";
+ data += message_text;
+ data += "\"}";
+
+ http::response resp = sendRequest(MINECRAFTDYNMAP_REQUEST_MESSAGE, &data);
+
+ if (resp.code == HTTP_CODE_OK) {
+ JSONROOT root(resp.data.c_str());
+ if (root != NULL) {
+ JSONNODE *error_ = json_get(root, "error");
+ if (error_ != NULL) {
+ std::string error = json_as_pstring(error_);
+ if (error == "none") {
+ return handleSuccess(__FUNCTION__);
+ }
+ else if (error == "not-allowed") {
+ UpdateChat(NULL, TranslateT("Message was not sent. Probably you are sending them too fast or chat is disabled completely."));
+ }
+ }
+ }
+ }
+
+ return handleError(__FUNCTION__);
+}
+
+std::string MinecraftDynmapProto::doGetPage(const int request_type)
+{
+ handleEntry(__FUNCTION__);
+
+ http::response resp = sendRequest(request_type);
+
+ if (resp.code == HTTP_CODE_OK) {
+ handleSuccess(__FUNCTION__);
+ } else {
+ handleError(__FUNCTION__);
+ }
+
+ return resp.data;
+}
+
+void MinecraftDynmapProto::SignOnWorker(void*)
+{
+ SYSTEMTIME t;
+ GetLocalTime(&t);
+ debugLogA("[%d.%d.%d] Using Omegle Protocol %s", t.wDay, t.wMonth, t.wYear, __VERSION_STRING_DOTS);
+
+ ScopedLock s(signon_lock_);
+
+ int old_status = m_iStatus;
+
+ // Load server from database
+ m_server = ptrA(db_get_sa(NULL, m_szModuleName, MINECRAFTDYNMAP_KEY_SERVER));
+
+ if (m_server.empty()) {
+ MessageBox(NULL, TranslateT("Set server address to connect."), m_tszUserName, MB_OK);
+ SetStatus(ID_STATUS_OFFLINE);
+ return;
+ }
+
+ // Fix format of given server
+ if (m_server.substr(0, 7) != "http://" && m_server.substr(0, 8) != "https://")
+ m_server = "http://" + m_server;
+ if (m_server.substr(m_server.length() - 1, 1) == "/")
+ m_server = m_server.substr(0, m_server.length() -1);
+
+ if (doSignOn()) {
+ // Signed in, switch to online, create chatroom and start events loop
+ m_iStatus = m_iDesiredStatus;
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, m_iStatus);
+
+ setDword("LogonTS", (DWORD)time(NULL));
+ ClearChat();
+ OnJoinChat(0, false);
+
+ ResetEvent(events_loop_event_);
+
+ ForkThread(&MinecraftDynmapProto::EventsLoop, this);
+ }
+ else {
+ // Some error
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_FAILED, (HANDLE)old_status, m_iStatus);
+ }
+
+}
+
+void MinecraftDynmapProto::SignOffWorker(void*)
+{
+ ScopedLock s(signon_lock_);
+
+ SetEvent(events_loop_event_);
+
+ m_cookie.clear();
+ m_title.clear();
+ m_server.clear();
+ m_timestamp.clear();
+
+ int old_status = m_iStatus;
+ m_iStatus = ID_STATUS_OFFLINE;
+
+ Netlib_Shutdown(hEventsConnection);
+
+ OnLeaveChat(NULL, NULL);
+
+ delSetting("LogonTS");
+
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, m_iStatus);
+
+ //SetAllContactStatuses(ID_STATUS_OFFLINE);
+ //ToggleStatusMenuItems(false);
+
+ if (hConnection)
+ Netlib_CloseHandle(hConnection);
+ hConnection = NULL;
+
+ if (hEventsConnection)
+ Netlib_CloseHandle(hEventsConnection);
+ hEventsConnection = NULL;
+}
+
+void MinecraftDynmapProto::EventsLoop(void *)
+{
+ ScopedLock s(events_loop_lock_);
+
+ time_t tim = ::time(NULL);
+ debugLogA(">>>>> Entering %s[%d]", __FUNCTION__, tim);
+
+ while (doEvents())
+ {
+ if (!isOnline())
+ break;
+
+ if (WaitForSingleObjectEx(events_loop_event_, m_updateRate, true) != WAIT_TIMEOUT) // FIXME: correct timeout
+ break;
+
+ debugLogA("***** %s[%d] refreshing...", __FUNCTION__, tim);
+ }
+
+ ResetEvent(events_loop_event_);
+ ResetEvent(events_loop_lock_);
+ debugLogA("<<<<< Exiting %s[%d]", __FUNCTION__, tim);
+}
+
+void MinecraftDynmapProto::SendMsgWorker(void *p)
+{
+ if (p == NULL)
+ return;
+
+ ScopedLock s(send_message_lock_);
+
+ std::string data = *(std::string*)p;
+ delete (std::string*)p;
+
+ data = utils::text::trim(data);
+
+ if (isOnline() && data.length()) {
+ doSendMessage(data);
+ }
+}