summaryrefslogtreecommitdiff
path: root/protocols/Gadu-Gadu/core.cpp
diff options
context:
space:
mode:
authorGeorge Hazan <george.hazan@gmail.com>2012-07-15 16:44:52 +0000
committerGeorge Hazan <george.hazan@gmail.com>2012-07-15 16:44:52 +0000
commit4649bcfc2b1cbbe2f004d7bec963a7528866c072 (patch)
treec6619f8f08266c740dc4e9783396f4f614319e1f /protocols/Gadu-Gadu/core.cpp
parent43691e8134fab2a55243607b2047c2fb3f3ee04b (diff)
z całym szacunkiem dla naszych polskich użytkowników
git-svn-id: http://svn.miranda-ng.org/main/trunk@977 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c
Diffstat (limited to 'protocols/Gadu-Gadu/core.cpp')
-rw-r--r--protocols/Gadu-Gadu/core.cpp1765
1 files changed, 1765 insertions, 0 deletions
diff --git a/protocols/Gadu-Gadu/core.cpp b/protocols/Gadu-Gadu/core.cpp
new file mode 100644
index 0000000000..735b3536bc
--- /dev/null
+++ b/protocols/Gadu-Gadu/core.cpp
@@ -0,0 +1,1765 @@
+////////////////////////////////////////////////////////////////////////////////
+// Gadu-Gadu Plugin for Miranda IM
+//
+// Copyright (c) 2003-2009 Adam Strzelecki <ono+miranda@java.pl>
+// Copyright (c) 2009-2012 Bartosz Bia³ek
+//
+// 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, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "gg.h"
+#include <errno.h>
+#include <io.h>
+
+////////////////////////////////////////////////////////////
+// Swap bits in DWORD
+uint32_t swap32(uint32_t x)
+{
+ return (uint32_t)
+ (((x & (uint32_t) 0x000000ffU) << 24) |
+ ((x & (uint32_t) 0x0000ff00U) << 8) |
+ ((x & (uint32_t) 0x00ff0000U) >> 8) |
+ ((x & (uint32_t) 0xff000000U) >> 24));
+}
+
+////////////////////////////////////////////////////////////
+// Is online function
+
+int GGPROTO::isonline()
+{
+ mir_cslock lck(sess_mutex);
+ return (sess != NULL);
+}
+
+////////////////////////////////////////////////////////////
+// Send disconnect request and wait for server thread to die
+void GGPROTO::disconnect()
+{
+ // If main loop then send disconnect request
+ if (isonline())
+ {
+ // Fetch proper status msg
+ char *szMsg = NULL;
+
+ // Loadup status
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_LEAVESTATUSMSG, GG_KEYDEF_LEAVESTATUSMSG))
+ {
+ DBVARIANT dbv;
+ switch (db_get_w(NULL, m_szModuleName, GG_KEY_LEAVESTATUS, GG_KEYDEF_LEAVESTATUS)) {
+ case ID_STATUS_ONLINE:
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(modemsg.online);
+ LeaveCriticalSection(&modemsg_mutex);
+ if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_ONLINE, "Default"), &dbv, DBVT_ASCIIZ)) {
+ if (dbv.pszVal && *(dbv.pszVal))
+ szMsg = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ break;
+ case ID_STATUS_AWAY:
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(modemsg.away);
+ LeaveCriticalSection(&modemsg_mutex);
+ if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_AWAY, "Default"), &dbv, DBVT_ASCIIZ)) {
+ if (dbv.pszVal && *(dbv.pszVal))
+ szMsg = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ break;
+ case ID_STATUS_DND:
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(modemsg.dnd);
+ LeaveCriticalSection(&modemsg_mutex);
+ if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_DND, "Default"), &dbv, DBVT_ASCIIZ)) {
+ if (dbv.pszVal && *(dbv.pszVal))
+ szMsg = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ break;
+ case ID_STATUS_FREECHAT:
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(modemsg.freechat);
+ LeaveCriticalSection(&modemsg_mutex);
+ if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_FREECHAT, "Default"), &dbv, DBVT_ASCIIZ)) {
+ if (dbv.pszVal && *(dbv.pszVal))
+ szMsg = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ break;
+ case ID_STATUS_INVISIBLE:
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(modemsg.invisible);
+ LeaveCriticalSection(&modemsg_mutex);
+ if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_INVISIBLE, "Default"), &dbv, DBVT_ASCIIZ)) {
+ if (dbv.pszVal && *(dbv.pszVal))
+ szMsg = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ break;
+ default:
+ // Set last status
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(getstatusmsg(m_iStatus));
+ LeaveCriticalSection(&modemsg_mutex);
+ }
+ }
+
+ EnterCriticalSection(&sess_mutex);
+ // Check if it has message
+ if (szMsg)
+ {
+ gg_change_status_descr(sess, GG_STATUS_NOT_AVAIL_DESCR, szMsg);
+ mir_free(szMsg);
+ // Wait for disconnection acknowledge
+ }
+ else
+ {
+ gg_change_status(sess, GG_STATUS_NOT_AVAIL);
+ // Send logoff immediately
+ gg_logoff(sess);
+ }
+ LeaveCriticalSection(&sess_mutex);
+ }
+ // Else cancel connection attempt
+ else if (sock)
+ closesocket(sock);
+}
+
+////////////////////////////////////////////////////////////
+// DNS lookup function
+uint32_t gg_dnslookup(GGPROTO *gg, char *host)
+{
+ uint32_t ip;
+ struct hostent *he;
+
+ ip = inet_addr(host);
+ if (ip != INADDR_NONE)
+ {
+#ifdef DEBUGMODE
+ gg->netlog("gg_dnslookup(): Parameter \"%s\" is already IP number.", host);
+#endif
+ return ip;
+ }
+ he = gethostbyname(host);
+ if (he)
+ {
+ ip = *(uint32_t *) he->h_addr_list[0];
+#ifdef DEBUGMODE
+ gg->netlog("gg_dnslookup(): Parameter \"%s\" was resolved to %d.%d.%d.%d.", host,
+ LOBYTE(LOWORD(ip)), HIBYTE(LOWORD(ip)), LOBYTE(HIWORD(ip)), HIBYTE(HIWORD(ip)));
+#endif
+ return ip;
+ }
+ gg->netlog("gg_dnslookup(): Cannot resolve hostname \"%s\".", host);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////
+// Host list decoder
+typedef struct
+{
+ char hostname[128];
+ int port;
+} GGHOST;
+#define ISHOSTALPHA(a) (((a) >= '0' && (a) <= '9') || ((a) >= 'a' && (a) <= 'z') || (a) == '.' || (a) == '-')
+int gg_decodehosts(char *var, GGHOST *hosts, int max)
+{
+ int hp = 0;
+ char *hostname = NULL;
+ char *portname = NULL;
+
+ while(var && *var && hp < max)
+ {
+ if (ISHOSTALPHA(*var))
+ {
+ hostname = var;
+
+ while(var && *var && ISHOSTALPHA(*var)) var ++;
+
+ if (var && *var == ':' && var++ && *var && isdigit(*var))
+ {
+ *(var - 1) = 0;
+ portname = var;
+ while(var && *var && isdigit(*var)) var++;
+ if (*var) { *var = 0; var ++; }
+ }
+ else
+ if (*var) { *var = 0; var ++; }
+
+ // Insert new item
+ hosts[hp].hostname[127] = 0;
+ strncpy(hosts[hp].hostname, hostname, 127);
+ hosts[hp].port = portname ? atoi(portname) : 443;
+ hp ++;
+
+ // Zero the names
+ hostname = NULL;
+ portname = NULL;
+ }
+ else
+ var ++;
+ }
+ return hp;
+}
+
+////////////////////////////////////////////////////////////
+// Main connection session thread
+void __cdecl GGPROTO::mainthread(void *)
+{
+ // Miranda variables
+ NETLIBUSERSETTINGS nlus = {0};
+ DBVARIANT dbv;
+ // Gadu-Gadu variables
+ gg_login_params p = {0};
+ gg_event *e;
+ // Host cycling variables
+ int hostnum = 0, hostcount = 0;
+ GGHOST hosts[64];
+ // Gadu-gadu login errors
+ static const struct tagReason { int type; TCHAR *str; } reason[] = {
+ { GG_FAILURE_RESOLVING, LPGENT("Miranda was unable to resolve the name of the Gadu-Gadu server to its numeric address.") },
+ { GG_FAILURE_CONNECTING, LPGENT("Miranda was unable to make a connection with a server. It is likely that the server is down, in which case you should wait for a while and try again later.") },
+ { GG_FAILURE_INVALID, LPGENT("Received invalid server response.") },
+ { GG_FAILURE_READING, LPGENT("The connection with the server was abortively closed during the connection attempt. You may have lost your local network connection.") },
+ { GG_FAILURE_WRITING, LPGENT("The connection with the server was abortively closed during the connection attempt. You may have lost your local network connection.") },
+ { GG_FAILURE_PASSWORD, LPGENT("Your Gadu-Gadu number and password combination was rejected by the Gadu-Gadu server. Please check login details at M->Options->Network->Gadu-Gadu and try again.") },
+ { GG_FAILURE_404, LPGENT("Connecting to Gadu-Gadu hub failed.") },
+ { GG_FAILURE_TLS, LPGENT("Cannot establish secure connection.") },
+ { GG_FAILURE_NEED_EMAIL, LPGENT("Server disconnected asking you for changing your e-mail.") },
+ { GG_FAILURE_INTRUDER, LPGENT("Too many login attempts with invalid password.") },
+ { GG_FAILURE_UNAVAILABLE, LPGENT("Gadu-Gadu servers are now down. Try again later.") },
+ { 0, LPGENT("Unknown") }
+ };
+ time_t logonTime = 0;
+ time_t timeDeviation = db_get_w(NULL, m_szModuleName, GG_KEY_TIMEDEVIATION, GG_KEYDEF_TIMEDEVIATION);
+ int gg_failno = 0;
+
+ netlog("gg_mainthread(%x): Server Thread Starting", this);
+#ifdef DEBUGMODE
+ gg_debug_level = GG_DEBUG_NET | GG_DEBUG_TRAFFIC | GG_DEBUG_FUNCTION | GG_DEBUG_MISC;
+#else
+ gg_debug_level = 0;
+#endif
+
+ // Broadcast that service is connecting
+ broadcastnewstatus(ID_STATUS_CONNECTING);
+
+ // Client version and misc settings
+ p.client_version = GG_DEFAULT_CLIENT_VERSION;
+ p.protocol_version = GG_DEFAULT_PROTOCOL_VERSION;
+ p.protocol_features = GG_FEATURE_DND_FFC | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION | GG_FEATURE_MULTILOGON;
+ p.encoding = GG_ENCODING_CP1250;
+ p.status_flags = GG_STATUS_FLAG_UNKNOWN;
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_SHOWLINKS, GG_KEYDEF_SHOWLINKS))
+ p.status_flags |= GG_STATUS_FLAG_SPAM;
+
+ // Use audio
+ /* p.has_audio = 1; */
+
+ // Use async connections
+ /* p.async = 1; */
+
+ // Send Era Omnix info if set
+ p.era_omnix = db_get_b(NULL, m_szModuleName, "EraOmnix", 0);
+
+ // Setup proxy
+ nlus.cbSize = sizeof(nlus);
+ if (CallService(MS_NETLIB_GETUSERSETTINGS, (WPARAM)netlib, (LPARAM)&nlus))
+ {
+ if (nlus.useProxy)
+ netlog("gg_mainthread(%x): Using proxy %s:%d.", this, nlus.szProxyServer, nlus.wProxyPort);
+ gg_proxy_enabled = nlus.useProxy;
+ gg_proxy_host = nlus.szProxyServer;
+ gg_proxy_port = nlus.wProxyPort;
+ if (nlus.useProxyAuth)
+ {
+ gg_proxy_username = nlus.szProxyAuthUser;
+ gg_proxy_password = nlus.szProxyAuthPassword;
+ }
+ else
+ gg_proxy_username = gg_proxy_password = NULL;
+ }
+ else
+ {
+ netlog("gg_mainthread(%x): Failed loading proxy settings.", this);
+ gg_proxy_enabled = 0;
+ }
+
+ // Check out manual host setting
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_MANUALHOST, GG_KEYDEF_MANUALHOST))
+ {
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_SERVERHOSTS, &dbv, DBVT_ASCIIZ))
+ {
+ hostcount = gg_decodehosts(dbv.pszVal, hosts, 64);
+ DBFreeVariant(&dbv);
+ }
+ }
+
+ // Readup password
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ))
+ {
+ CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal);
+ p.password = mir_strdup(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else
+ {
+ netlog("gg_mainthread(%x): No password specified. Exiting.", this);
+ broadcastnewstatus(ID_STATUS_OFFLINE);
+ return;
+ }
+
+ // Readup number
+ if (!(p.uin = db_get_b(NULL, m_szModuleName, GG_KEY_UIN, 0)))
+ {
+ netlog("gg_mainthread(%x): No Gadu-Gadu number specified. Exiting.", this);
+ broadcastnewstatus(ID_STATUS_OFFLINE);
+ mir_free(p.password);
+ return;
+ }
+
+ // Readup SSL/TLS setting
+ if (p.tls = db_get_b(NULL, m_szModuleName, GG_KEY_SSLCONN, GG_KEYDEF_SSLCONN))
+ netlog("gg_mainthread(%x): Using TLS/SSL for connections.", this);
+
+ // Gadu-Gadu accepts image sizes upto 255
+ p.image_size = 255;
+
+ ////////////////////////////// DCC STARTUP /////////////////////////////
+ // Uin is ok so startup dcc if not started already
+ if (!dcc)
+ {
+ hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ dccstart();
+
+ // Wait for DCC
+#ifdef DEBUGMODE
+ netlog("gg_mainthread(%x): Waiting DCC service to start...", this);
+#endif
+ while (WaitForSingleObjectEx(hEvent, INFINITE, TRUE) != WAIT_OBJECT_0);
+ CloseHandle(hEvent); hEvent = NULL;
+ }
+ // Check if dcc is running and setup forwarding port
+ if (dcc && db_get_b(NULL, m_szModuleName, GG_KEY_FORWARDING, GG_KEYDEF_FORWARDING))
+ {
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_FORWARDHOST, &dbv, DBVT_ASCIIZ))
+ {
+ if (!(p.external_addr = gg_dnslookup(this, dbv.pszVal)))
+ {
+ TCHAR error[128];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("External direct connections hostname %S is invalid. Disabling external host forwarding."), dbv.pszVal);
+ showpopup(m_tszUserName, error, GG_POPUP_WARNING | GG_POPUP_ALLOW_MSGBOX);
+ }
+ else
+ netlog("gg_mainthread(%x): Loading forwarding host %s and port %d.", dbv.pszVal, p.external_port, this);
+ if (p.external_addr) p.external_port = db_get_w(NULL, m_szModuleName, GG_KEY_FORWARDPORT, GG_KEYDEF_FORWARDPORT);
+ DBFreeVariant(&dbv);
+ }
+ }
+ // Setup client port
+ if (dcc) p.client_port = dcc->port;
+
+retry:
+ // Loadup startup status & description
+ EnterCriticalSection(&modemsg_mutex);
+ p.status_descr = mir_strdup(getstatusmsg(m_iDesiredStatus));
+ p.status = status_m2gg(m_iDesiredStatus, p.status_descr != NULL);
+
+ netlog("gg_mainthread(%x): Connecting with number %d, status %d and description \"%s\".", this, p.uin, m_iDesiredStatus,
+ p.status_descr ? p.status_descr : "<none>");
+ LeaveCriticalSection(&modemsg_mutex);
+
+ // Check manual hosts
+ if (hostnum < hostcount)
+ {
+ if (!(p.server_addr = gg_dnslookup(this, hosts[hostnum].hostname)))
+ {
+ TCHAR error[128];
+ mir_sntprintf(error, SIZEOF(error), TranslateT("Server hostname %S is invalid. Using default hostname provided by the network."), hosts[hostnum].hostname);
+ showpopup(m_tszUserName, error, GG_POPUP_WARNING | GG_POPUP_ALLOW_MSGBOX);
+ }
+ else
+ {
+ p.server_port = hosts[hostnum].port;
+ netlog("gg_mainthread(%x): Connecting to manually specified host %s (%d.%d.%d.%d) and port %d.", this,
+ hosts[hostnum].hostname, LOBYTE(LOWORD(p.server_addr)), HIBYTE(LOWORD(p.server_addr)),
+ LOBYTE(HIWORD(p.server_addr)), HIBYTE(HIWORD(p.server_addr)), p.server_port);
+ }
+ }
+ else
+ p.server_port = p.server_addr = 0;
+
+ // Send login request
+ if (!(sess = gg_login(&p, &sock, &gg_failno)))
+ {
+ broadcastnewstatus(ID_STATUS_OFFLINE);
+ // Check if connection attempt wasn't cancelled by the user
+ if (m_iDesiredStatus != ID_STATUS_OFFLINE)
+ {
+ TCHAR error[128], *perror = NULL;
+ // Lookup for error desciption
+ if (errno == EACCES) {
+ for (int i = 0; reason[i].type; i++) if (reason[i].type == gg_failno) {
+ perror = TranslateTS(reason[i].str);
+ break;
+ }
+ }
+ if (!perror) {
+ mir_sntprintf(error, SIZEOF(error), TranslateT("Connection cannot be established because of error:\n\t%s"), _tcserror(errno));
+ perror = error;
+ }
+ netlog("gg_mainthread(%x): %s", this, perror);
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_SHOWCERRORS, GG_KEYDEF_SHOWCERRORS))
+ showpopup(m_tszUserName, perror, GG_POPUP_ERROR | GG_POPUP_ALLOW_MSGBOX | GG_POPUP_ONCE);
+
+ // Check if we should reconnect
+ if ((gg_failno >= GG_FAILURE_RESOLVING && gg_failno != GG_FAILURE_PASSWORD && gg_failno != GG_FAILURE_INTRUDER && gg_failno != GG_FAILURE_UNAVAILABLE)
+ && errno == EACCES
+ && (db_get_b(NULL, m_szModuleName, GG_KEY_ARECONNECT, GG_KEYDEF_ARECONNECT) || (hostnum < hostcount - 1)))
+ {
+ DWORD dwInterval = db_get_b(NULL, m_szModuleName, GG_KEY_RECONNINTERVAL, GG_KEYDEF_RECONNINTERVAL), dwResult;
+ BOOL bRetry = TRUE;
+
+ hConnStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ dwResult = WaitForSingleObjectEx(hConnStopEvent, dwInterval, TRUE);
+ if ((dwResult == WAIT_OBJECT_0 && m_iDesiredStatus == ID_STATUS_OFFLINE)
+ || (dwResult == WAIT_IO_COMPLETION && Miranda_Terminated()))
+ bRetry = FALSE;
+ CloseHandle(hConnStopEvent);
+ hConnStopEvent = NULL;
+
+ // Reconnect to the next server on the list
+ if (bRetry)
+ {
+ if (hostnum < hostcount - 1) hostnum++;
+ mir_free(p.status_descr);
+ broadcastnewstatus(ID_STATUS_CONNECTING);
+ goto retry;
+ }
+ }
+ // We cannot do more about this
+ EnterCriticalSection(&modemsg_mutex);
+ m_iDesiredStatus = ID_STATUS_OFFLINE;
+ LeaveCriticalSection(&modemsg_mutex);
+ }
+ else
+ netlog("gg_mainthread(%x)): Connection attempt cancelled by the user.", this);
+ }
+ else
+ {
+ // Successfully connected
+ logonTime = time(NULL);
+ db_set_w(NULL, m_szModuleName, GG_KEY_LOGONTIME, logonTime);
+ EnterCriticalSection(&sess_mutex);
+ sess = sess;
+ LeaveCriticalSection(&sess_mutex);
+ // Subscribe users status notifications
+ notifyall();
+ // Set startup status
+ if (m_iDesiredStatus != status_gg2m(p.status))
+ refreshstatus(m_iDesiredStatus);
+ else
+ {
+ broadcastnewstatus(m_iDesiredStatus);
+ // Change status of the contact with our own UIN (if got yourself added to the contact list)
+ changecontactstatus(p.uin, p.status, p.status_descr, 0, 0, 0, 0);
+ }
+ if (check_first_conn) // First connection to the account
+ {
+ // Start search for user data
+ GetInfo(NULL, 0);
+ // Fetch user avatar
+ getUserAvatar();
+ check_first_conn = 0;
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////
+ // Main loop
+ while(isonline())
+ {
+ // Connection broken/closed
+ if (!(e = gg_watch_fd(sess)))
+ {
+ netlog("gg_mainthread(%x): Connection closed.", this);
+ EnterCriticalSection(&sess_mutex);
+ gg_free_session(sess);
+ sess = NULL;
+ LeaveCriticalSection(&sess_mutex);
+ break;
+ }
+ else
+ netlog("gg_mainthread(%x): Event: %s", this, ggdebug_eventtype(e));
+
+ switch(e->type)
+ {
+ // Client connected
+ case GG_EVENT_CONN_SUCCESS:
+ // Nada
+ break;
+
+ // Client disconnected or connection failure
+ case GG_EVENT_CONN_FAILED:
+ case GG_EVENT_DISCONNECT:
+ EnterCriticalSection(&sess_mutex);
+ gg_free_session(sess);
+ sess = NULL;
+ LeaveCriticalSection(&sess_mutex);
+ break;
+
+ // Client allowed to disconnect
+ case GG_EVENT_DISCONNECT_ACK:
+ // Send logoff
+ gg_logoff(sess);
+ break;
+
+ // Received ackowledge
+ case GG_EVENT_ACK:
+ if (e->event.ack.seq && e->event.ack.recipient)
+ {
+ ProtoBroadcastAck(m_szModuleName, getcontact((DWORD)e->event.ack.recipient, 0, 0, NULL),
+ ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE) e->event.ack.seq, 0);
+ }
+ break;
+
+ // Statuslist notify (deprecated)
+ case GG_EVENT_NOTIFY:
+ case GG_EVENT_NOTIFY_DESCR:
+ {
+ struct gg_notify_reply *n;
+
+ n = (e->type == GG_EVENT_NOTIFY) ? e->event.notify : e->event.notify_descr.notify;
+
+ for (; n->uin; n++)
+ {
+ char *descr = (e->type == GG_EVENT_NOTIFY_DESCR) ? e->event.notify_descr.descr : NULL;
+ changecontactstatus(n->uin, n->status, descr, 0, n->remote_ip, n->remote_port, n->version);
+ }
+ break;
+ }
+ // Statuslist notify (version >= 6.0)
+ case GG_EVENT_NOTIFY60:
+ {
+ uin_t uin = (uin_t)db_get_b(NULL, m_szModuleName, GG_KEY_UIN, 0);
+ int i;
+ for(i = 0; e->event.notify60[i].uin; i++) {
+ if (e->event.notify60[i].uin == uin) continue;
+ changecontactstatus(e->event.notify60[i].uin, e->event.notify60[i].status, e->event.notify60[i].descr,
+ e->event.notify60[i].time, e->event.notify60[i].remote_ip, e->event.notify60[i].remote_port,
+ e->event.notify60[i].version);
+ requestAvatar(getcontact(e->event.notify60[i].uin, 0, 0, NULL), 0);
+ }
+ break;
+ }
+
+ // Pubdir search reply && read own data reply
+ case GG_EVENT_PUBDIR50_SEARCH_REPLY:
+ case GG_EVENT_PUBDIR50_READ:
+ case GG_EVENT_PUBDIR50_WRITE:
+ {
+ gg_pubdir50_t res = e->event.pubdir50;
+ int i, count;
+
+ if (e->type == GG_EVENT_PUBDIR50_SEARCH_REPLY)
+ {
+ netlog("gg_mainthread(%x): Got user info.", this);
+ // Store next search UIN
+ if (res->seq == GG_SEQ_SEARCH)
+ next_uin = gg_pubdir50_next(res);
+ }
+ else if (e->type == GG_EVENT_PUBDIR50_READ)
+ {
+ netlog("gg_mainthread(%x): Got owner info.", this);
+ }
+ else if (e->type == GG_EVENT_PUBDIR50_WRITE)
+ {
+ netlog("gg_mainthread(%x): Public directory save succesful.", this);
+ // Update user details
+ GetInfo(NULL, 0);
+ }
+
+ if ((count = gg_pubdir50_count(res)) > 0)
+ {
+ for (i = 0; i < count; i++)
+ {
+ // Loadup fields
+ const char *__fmnumber = gg_pubdir50_get(res, i, GG_PUBDIR50_UIN);
+ const char *__nick = gg_pubdir50_get(res, i, GG_PUBDIR50_NICKNAME);
+ const char *__firstname = gg_pubdir50_get(res, i, GG_PUBDIR50_FIRSTNAME);
+ const char *__lastname = gg_pubdir50_get(res, i, GG_PUBDIR50_LASTNAME);
+ const char *__familyname = gg_pubdir50_get(res, i, GG_PUBDIR50_FAMILYNAME);
+ const char *__birthyear = gg_pubdir50_get(res, i, GG_PUBDIR50_BIRTHYEAR);
+ const char *__city = gg_pubdir50_get(res, i, GG_PUBDIR50_CITY);
+ const char *__origincity = gg_pubdir50_get(res, i, GG_PUBDIR50_FAMILYCITY);
+ const char *__gender = gg_pubdir50_get(res, i, GG_PUBDIR50_GENDER);
+ const char *__status = gg_pubdir50_get(res, i, GG_PUBDIR50_STATUS);
+ uin_t uin = __fmnumber ? atoi(__fmnumber) : 0;
+
+ HANDLE hContact = (res->seq == GG_SEQ_CHINFO) ? NULL : getcontact(uin, 0, 0, NULL);
+ netlog("gg_mainthread(%x): Search result for uin %d, seq %d.", this, uin, res->seq);
+ if (res->seq == GG_SEQ_SEARCH)
+ {
+ char strFmt1[64];
+ char strFmt2[64];
+
+ mir_snprintf(strFmt2, sizeof(strFmt2), "%s", (char *)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, status_gg2m(atoi(__status)), 0));
+ if (__city)
+ {
+ mir_snprintf(strFmt1, sizeof(strFmt1), ", %s %s", Translate("City:"), __city);
+ strncat(strFmt2, strFmt1, sizeof(strFmt2) - strlen(strFmt2));
+ }
+ if (__birthyear)
+ {
+ time_t t = time(NULL);
+ struct tm *lt = localtime(&t);
+ int br = atoi(__birthyear);
+
+ if (br < (lt->tm_year + 1900) && br > 1900)
+ {
+ mir_snprintf(strFmt1, sizeof(strFmt1), ", %s %d", Translate("Age:"), (lt->tm_year + 1900) - br);
+ strncat(strFmt2, strFmt1, sizeof(strFmt2) - strlen(strFmt2));
+ }
+ }
+
+ GGSEARCHRESULT sr;
+ memset(&sr, 0, sizeof(sr));
+ sr.cbSize = sizeof(sr);
+ sr.nick = mir_a2t(__nick);
+ sr.firstName = mir_a2t(__firstname);
+ sr.lastName = mir_a2t(__lastname);
+ sr.email = mir_a2t(strFmt2);
+ sr.id = mir_a2t(_ultoa(uin, strFmt1, 10));
+ sr.uin = uin;
+ ProtoBroadcastAck(m_szModuleName, NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE) 1, (LPARAM)&sr);
+ mir_free(sr.nick);
+ mir_free(sr.firstName);
+ mir_free(sr.lastName);
+ mir_free(sr.email);
+ mir_free(sr.id);
+ }
+
+ if (((res->seq == GG_SEQ_INFO || res->seq == GG_SEQ_GETNICK) && hContact != NULL)
+ || res->seq == GG_SEQ_CHINFO)
+ {
+ // Change nickname if it's not present
+ if (__nick && (res->seq == GG_SEQ_GETNICK || res->seq == GG_SEQ_CHINFO))
+ db_set_s(hContact, m_szModuleName, GG_KEY_NICK, __nick);
+
+ if (__nick)
+ db_set_s(hContact, m_szModuleName, "NickName", __nick);
+ else if (res->seq == GG_SEQ_CHINFO)
+ db_unset(NULL, m_szModuleName, "NickName");
+
+ // Change other info
+ if (__city)
+ db_set_s(hContact, m_szModuleName, "City", __city);
+ else if (res->seq == GG_SEQ_CHINFO)
+ db_unset(NULL, m_szModuleName, "City");
+
+ if (__firstname)
+ db_set_s(hContact, m_szModuleName, "FirstName", __firstname);
+ else if (res->seq == GG_SEQ_CHINFO)
+ db_unset(NULL, m_szModuleName, "FirstName");
+
+ if (__lastname)
+ db_set_s(hContact, m_szModuleName, "LastName", __lastname);
+ else if (res->seq == GG_SEQ_CHINFO)
+ db_unset(NULL, m_szModuleName, "LastName");
+
+ if (__familyname)
+ db_set_s(hContact, m_szModuleName, "FamilyName", __familyname);
+ else if (res->seq == GG_SEQ_CHINFO)
+ db_unset(NULL, m_szModuleName, "FamilyName");
+
+ if (__origincity)
+ db_set_s(hContact, m_szModuleName, "CityOrigin", __origincity);
+ else if (res->seq == GG_SEQ_CHINFO)
+ db_unset(NULL, m_szModuleName, "CityOrigin");
+
+ if (__birthyear)
+ {
+ time_t t = time(NULL);
+ struct tm *lt = localtime(&t);
+ int br = atoi(__birthyear);
+ if (br > 0)
+ {
+ db_set_w(hContact, m_szModuleName, "Age", (WORD)(lt->tm_year + 1900 - br));
+ db_set_w(hContact, m_szModuleName, "BirthYear", (WORD)br);
+ }
+ }
+ else if (res->seq == GG_SEQ_CHINFO)
+ {
+ db_unset(NULL, m_szModuleName, "Age");
+ db_unset(NULL, m_szModuleName, "BirthYear");
+ }
+
+ // Gadu-Gadu Male <-> Female
+ if (__gender)
+ {
+ if (res->seq == GG_SEQ_CHINFO)
+ db_set_b(hContact, m_szModuleName, "Gender",
+ (BYTE)(!strcmp(__gender, GG_PUBDIR50_GENDER_SET_MALE) ? 'M' :
+ (!strcmp(__gender, GG_PUBDIR50_GENDER_SET_FEMALE) ? 'F' : '?')));
+ else
+ db_set_b(hContact, m_szModuleName, "Gender",
+ (BYTE)(!strcmp(__gender, GG_PUBDIR50_GENDER_MALE) ? 'M' :
+ (!strcmp(__gender, GG_PUBDIR50_GENDER_FEMALE) ? 'F' : '?')));
+ }
+ else if (res->seq == GG_SEQ_CHINFO)
+ {
+ db_unset(NULL, m_szModuleName, "Gender");
+ }
+
+ netlog("gg_mainthread(%x): Setting user info for uin %d.", this, uin);
+ ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE) 1, 0);
+ }
+ }
+ }
+ if (res->seq == GG_SEQ_SEARCH)
+ ProtoBroadcastAck(m_szModuleName, NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE) 1, 0);
+ break;
+ }
+
+ // Status (deprecated)
+ case GG_EVENT_STATUS:
+ changecontactstatus(e->event.status.uin, e->event.status.status, e->event.status.descr, 0, 0, 0, 0);
+ break;
+
+ // Status (version >= 6.0)
+ case GG_EVENT_STATUS60:
+ {
+ HANDLE hContact = getcontact(e->event.status60.uin, 0, 0, NULL);
+ int oldstatus = db_get_w(hContact, m_szModuleName, GG_KEY_STATUS, (WORD)ID_STATUS_OFFLINE);
+ uin_t uin = (uin_t)db_get_b(NULL, m_szModuleName, GG_KEY_UIN, 0);
+
+ if (e->event.status60.uin == uin)
+ {
+ // Status was changed by the user simultaneously logged on using different Miranda account or IM client
+ int iStatus = status_gg2m(e->event.status60.status);
+ CallProtoService(m_szModuleName, PS_SETAWAYMSG, iStatus, (LPARAM)e->event.status60.descr);
+ CallProtoService(m_szModuleName, PS_SETSTATUS, iStatus, 0);
+ }
+
+ changecontactstatus(e->event.status60.uin, e->event.status60.status, e->event.status60.descr,
+ e->event.status60.time, e->event.status60.remote_ip, e->event.status60.remote_port, e->event.status60.version);
+
+ if (oldstatus == ID_STATUS_OFFLINE && db_get_w(hContact, m_szModuleName, GG_KEY_STATUS, (WORD)ID_STATUS_OFFLINE) != ID_STATUS_OFFLINE)
+ requestAvatar(hContact, 0);
+ }
+ break;
+
+ // Received userlist / or put info
+ case GG_EVENT_USERLIST:
+ switch (e->event.userlist.type) {
+ case GG_USERLIST_GET_REPLY:
+ if (e->event.userlist.reply) {
+ parsecontacts(e->event.userlist.reply);
+ MessageBox(NULL, TranslateT("List import successful."), m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ }
+ break;
+
+ case GG_USERLIST_PUT_REPLY:
+ if (is_list_remove)
+ MessageBox(NULL, TranslateT("List remove successful."), m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ else
+ MessageBox(NULL, TranslateT("List export successful."), m_tszUserName, MB_OK | MB_ICONINFORMATION);
+ break;
+ }
+ break;
+
+ // Received message
+ case GG_EVENT_MSG:
+ // This is CTCP request
+ if ((e->event.msg.msgclass & GG_CLASS_CTCP))
+ {
+ dccconnect(e->event.msg.sender);
+ }
+ // Check if not conference and block
+ else if (!e->event.msg.recipients_count || gc_enabled)
+ {
+ // Check if groupchat
+ if (e->event.msg.recipients_count && gc_enabled && !db_get_b(NULL, m_szModuleName, GG_KEY_IGNORECONF, GG_KEYDEF_IGNORECONF))
+ {
+ char *chat = gc_getchat(e->event.msg.sender, e->event.msg.recipients, e->event.msg.recipients_count);
+ if (chat)
+ {
+ char id[32];
+ GCDEST gcdest = {m_szModuleName, chat, GC_EVENT_MESSAGE};
+ GCEVENT gcevent = {sizeof(GCEVENT), &gcdest};
+ time_t t = time(NULL);
+
+ UIN2ID(e->event.msg.sender, id);
+
+ gcevent.pszUID = id;
+ gcevent.pszText = e->event.msg.message;
+ gcevent.pszNick = (char *) CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM) getcontact(e->event.msg.sender, 1, 0, NULL), 0);
+ gcevent.time = (!(e->event.msg.msgclass & GG_CLASS_OFFLINE) || e->event.msg.time > (t - timeDeviation)) ? t : e->event.msg.time;
+ gcevent.dwFlags = GCEF_ADDTOLOG;
+ netlog("gg_mainthread(%x): Conference message to room %s & id %s.", this, chat, id);
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent);
+ }
+ }
+ // Check if not empty message ( who needs it? )
+ else if (!e->event.msg.recipients_count && e->event.msg.message && *e->event.msg.message && strcmp(e->event.msg.message, "\xA0\0"))
+ {
+ CCSDATA ccs = {0};
+ PROTORECVEVENT pre = {0};
+ time_t t = time(NULL);
+ ccs.szProtoService = PSR_MESSAGE;
+ ccs.hContact = getcontact(e->event.msg.sender, 1, 0, NULL);
+ ccs.lParam = (LPARAM)&pre;
+ pre.timestamp = (!(e->event.msg.msgclass & GG_CLASS_OFFLINE) || e->event.msg.time > (t - timeDeviation)) ? t : e->event.msg.time;
+ pre.szMessage = e->event.msg.message;
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM) &ccs);
+ }
+
+ // RichEdit format included (image)
+ if (e->event.msg.formats_length &&
+ db_get_b(NULL, m_szModuleName, GG_KEY_IMGRECEIVE, GG_KEYDEF_IMGRECEIVE) &&
+ !(db_get_b(getcontact(e->event.msg.sender, 1, 0, NULL), "Ignore", "Mask1", 0) & IGNOREEVENT_MESSAGE))
+ {
+ char *formats = (char*)e->event.msg.formats;
+ int len = 0, formats_len = e->event.msg.formats_length, add_ptr;
+
+ while (len < formats_len)
+ {
+ add_ptr = sizeof(struct gg_msg_richtext_format);
+ if (((struct gg_msg_richtext_format*)formats)->font & GG_FONT_IMAGE)
+ {
+ struct gg_msg_richtext_image *image = (struct gg_msg_richtext_image *)(formats + add_ptr);
+ EnterCriticalSection(&sess_mutex);
+ gg_image_request(sess, e->event.msg.sender, image->size, image->crc32);
+ LeaveCriticalSection(&sess_mutex);
+
+ netlog("gg_mainthread: image request sent!");
+ add_ptr += sizeof(struct gg_msg_richtext_image);
+ }
+ if (((struct gg_msg_richtext_format*)formats)->font & GG_FONT_COLOR)
+ add_ptr += sizeof(struct gg_msg_richtext_color);
+ len += add_ptr;
+ formats += add_ptr;
+ }
+ }
+ }
+ break;
+
+ // Message sent from concurrent user session
+ case GG_EVENT_MULTILOGON_MSG:
+ if (e->event.multilogon_msg.recipients_count && gc_enabled && !db_get_b(NULL, m_szModuleName, GG_KEY_IGNORECONF, GG_KEYDEF_IGNORECONF))
+ {
+ char *chat = gc_getchat(e->event.multilogon_msg.sender, e->event.multilogon_msg.recipients, e->event.multilogon_msg.recipients_count);
+ if (chat)
+ {
+ char id[32];
+ DBVARIANT dbv;
+ GCDEST gcdest = {m_szModuleName, chat, GC_EVENT_MESSAGE};
+ GCEVENT gcevent = {sizeof(GCEVENT), &gcdest};
+
+ UIN2ID(db_get_b(NULL, m_szModuleName, GG_KEY_UIN, 0), id);
+
+ gcevent.pszUID = id;
+ gcevent.pszText = e->event.multilogon_msg.message;
+ if (!db_get_s(NULL, m_szModuleName, GG_KEY_NICK, &dbv, DBVT_ASCIIZ))
+ gcevent.pszNick = dbv.pszVal;
+ else
+ gcevent.pszNick = Translate("Me");
+ gcevent.time = e->event.multilogon_msg.time;
+ gcevent.bIsMe = 1;
+ gcevent.dwFlags = GCEF_ADDTOLOG;
+ netlog("gg_mainthread(%x): Sent conference message to room %s.", this, chat);
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent);
+ if (gcevent.pszNick == dbv.pszVal) DBFreeVariant(&dbv);
+ }
+ }
+ else if (!e->event.multilogon_msg.recipients_count && e->event.multilogon_msg.message && *e->event.multilogon_msg.message
+ && strcmp(e->event.multilogon_msg.message, "\xA0\0"))
+ {
+ DBEVENTINFO dbei = {0};
+ dbei.cbSize = sizeof(dbei);
+ dbei.szModule = m_szModuleName;
+ dbei.timestamp = (DWORD)e->event.multilogon_msg.time;
+ dbei.flags = DBEF_SENT;
+ dbei.eventType = EVENTTYPE_MESSAGE;
+ dbei.cbBlob = (DWORD)strlen(e->event.multilogon_msg.message) + 1;
+ dbei.pBlob = (PBYTE)e->event.multilogon_msg.message;
+ CallService(MS_DB_EVENT_ADD, (WPARAM)getcontact(e->event.multilogon_msg.sender, 1, 0, NULL), (LPARAM)&dbei);
+ }
+ break;
+
+ // Information on active concurrent sessions
+ case GG_EVENT_MULTILOGON_INFO:
+ {
+ list_t l;
+ int* iIndexes = NULL, i;
+ netlog("gg_mainthread(): Concurrent sessions count: %d.", e->event.multilogon_info.count);
+ if (e->event.multilogon_info.count > 0)
+ iIndexes = (int*)mir_calloc(e->event.multilogon_info.count * sizeof(int));
+ EnterCriticalSection(&sessions_mutex);
+ for (l = sessions; l; l = l->next)
+ {
+ struct gg_multilogon_session* sess = (struct gg_multilogon_session*)l->data;
+ for (i = 0; i < e->event.multilogon_info.count; i++)
+ {
+ if (!memcmp(&sess->id, &e->event.multilogon_info.sessions[i].id, sizeof(gg_multilogon_id_t)) && iIndexes)
+ {
+ iIndexes[i]++;
+ break;
+ }
+ }
+ mir_free(sess->name);
+ mir_free(sess);
+ }
+ list_destroy(sessions, 0);
+ sessions = NULL;
+ for (i = 0; i < e->event.multilogon_info.count; i++)
+ {
+ gg_multilogon_session* sess = (gg_multilogon_session*)mir_alloc(sizeof(struct gg_multilogon_session));
+ memcpy(sess, &e->event.multilogon_info.sessions[i], sizeof(struct gg_multilogon_session));
+ sess->name = mir_strdup(*e->event.multilogon_info.sessions[i].name != '\0'
+ ? e->event.multilogon_info.sessions[i].name
+ : Translate("Unknown client"));
+ list_add(&sessions, sess, 0);
+ }
+ LeaveCriticalSection(&sessions_mutex);
+ sessions_updatedlg();
+ if (ServiceExists(MS_POPUP_ADDPOPUPCLASS))
+ {
+ const TCHAR* szText = time(NULL) - logonTime > 3
+ ? TranslateT("You have logged in at another location")
+ : TranslateT("You are logged in at another location");
+ for (i = 0; i < e->event.multilogon_info.count; i++)
+ {
+ TCHAR szMsg[MAX_SECONDLINE];
+ if (iIndexes && iIndexes[i])
+ continue;
+
+ mir_sntprintf(szMsg, SIZEOF(szMsg), _T("%s (%s)"), szText,
+ *e->event.multilogon_info.sessions[i].name != '\0' ?
+ _A2T(e->event.multilogon_info.sessions[i].name) : TranslateT("Unknown client"));
+ showpopup(m_tszUserName, szMsg, GG_POPUP_MULTILOGON);
+ }
+ }
+ mir_free(iIndexes);
+ }
+ break;
+
+ // Image reply sent
+ case GG_EVENT_IMAGE_REPLY:
+ // Get rid of empty image
+ if (e->event.image_reply.size && e->event.image_reply.image)
+ {
+ HANDLE hContact = getcontact(e->event.image_reply.sender, 1, 0, NULL);
+ void *img = (void *)img_loadpicture(e, 0);
+
+ if (!img)
+ break;
+
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_IMGMETHOD, GG_KEYDEF_IMGMETHOD) == 1 || img_opened(e->event.image_reply.sender))
+ {
+ img_display(hContact, img);
+ }
+ else if (db_get_b(NULL, m_szModuleName, GG_KEY_IMGMETHOD, GG_KEYDEF_IMGMETHOD) == 2)
+ {
+ img_displayasmsg(hContact, img);
+ }
+ else
+ {
+ CLISTEVENT cle = {0};
+ char service[128];
+ mir_snprintf(service, sizeof(service), GGS_RECVIMAGE, m_szModuleName);
+
+ cle.cbSize = sizeof(cle);
+ cle.hContact = hContact;
+ cle.hIcon = LoadIconEx("image", FALSE);
+ cle.flags = CLEF_URGENT;
+ cle.hDbEvent = (HANDLE)"img";
+ cle.lParam = (LPARAM)img;
+ cle.pszService = service;
+ cle.pszTooltip = Translate("Incoming image");
+ CallService(MS_CLIST_ADDEVENT, 0, (LPARAM)&cle);
+ ReleaseIconEx("image", FALSE);
+ }
+ }
+ break;
+
+ // Image send request
+ case GG_EVENT_IMAGE_REQUEST:
+ img_sendonrequest(e);
+ break;
+
+ // Incoming direct connection
+ case GG_EVENT_DCC7_NEW:
+ {
+ struct gg_dcc7 *dcc7 = e->event.dcc7_new;
+ netlog("gg_mainthread(%x): Incoming direct connection.", this);
+ dcc7->contact = getcontact(dcc7->peer_uin, 0, 0, NULL);
+
+ // Check if user is on the list and if it is my uin
+ if (!dcc7->contact || db_get_b(NULL, m_szModuleName, GG_KEY_UIN, -1) != dcc7->uin) {
+ gg_dcc7_free(dcc7);
+ e->event.dcc7_new = NULL;
+ break;
+ }
+
+ // Add to waiting transfers
+ EnterCriticalSection(&ft_mutex);
+ list_add(&transfers, dcc7, 0);
+ LeaveCriticalSection(&ft_mutex);
+
+ //////////////////////////////////////////////////
+ // Add file recv request
+ {
+ CCSDATA ccs;
+ PROTORECVEVENT pre;
+ char *szBlob;
+ char *szFilename = (char*)dcc7->filename;
+ char *szMsg = (char*)dcc7->filename;
+ netlog("gg_mainthread(%x): Client: %d, File ack filename \"%s\" size %d.", this, dcc7->peer_uin,
+ dcc7->filename, dcc7->size);
+ // Make new ggtransfer struct
+ szBlob = (char *)malloc(sizeof(DWORD) + strlen(szFilename) + strlen(szMsg) + 2);
+ // Store current dcc
+ *(PDWORD)szBlob = (DWORD)dcc7;
+ // Store filename
+ strcpy(szBlob + sizeof(DWORD), szFilename);
+ // Store description
+ strcpy(szBlob + sizeof(DWORD) + strlen(szFilename) + 1, szMsg);
+ ccs.szProtoService = PSR_FILE;
+ ccs.hContact = dcc7->contact;
+ ccs.wParam = 0;
+ ccs.lParam = (LPARAM)&pre;
+ pre.flags = 0;
+ pre.timestamp = time(NULL);
+ pre.szMessage = szBlob;
+ pre.lParam = 0;
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs);
+ free(szBlob);
+ }
+ e->event.dcc7_new = NULL;
+ }
+ break;
+
+ // Direct connection rejected
+ case GG_EVENT_DCC7_REJECT:
+ {
+ struct gg_dcc7 *dcc7 = e->event.dcc7_reject.dcc7;
+ if (dcc7->type == GG_SESSION_DCC7_SEND)
+ {
+ netlog("gg_mainthread(%x): File transfer denied by client %d (reason = %d).", this, dcc7->peer_uin, e->event.dcc7_reject.reason);
+ ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_DENIED, dcc7, 0);
+
+ // Remove from watches and free
+ EnterCriticalSection(&ft_mutex);
+ list_remove(&watches, dcc7, 0);
+ LeaveCriticalSection(&ft_mutex);
+ gg_dcc7_free(dcc7);
+ }
+ else
+ {
+ netlog("gg_mainthread(%x): File transfer aborted by client %d.", this, dcc7->peer_uin);
+
+ // Remove transfer from waiting list
+ EnterCriticalSection(&ft_mutex);
+ list_remove(&transfers, dcc7, 0);
+ LeaveCriticalSection(&ft_mutex);
+ }
+ }
+ break;
+
+ // Direct connection error
+ case GG_EVENT_DCC7_ERROR:
+ {
+ struct gg_dcc7 *dcc7 = e->event.dcc7_error_ex.dcc7;
+ switch (e->event.dcc7_error)
+ {
+ case GG_ERROR_DCC7_HANDSHAKE:
+ netlog("gg_mainthread(%x): Client: %d, Handshake error.", this, dcc7 ? dcc7->peer_uin : 0);
+ break;
+ case GG_ERROR_DCC7_NET:
+ netlog("gg_mainthread(%x): Client: %d, Network error.", this, dcc7 ? dcc7->peer_uin : 0);
+ break;
+ case GG_ERROR_DCC7_FILE:
+ netlog("gg_mainthread(%x): Client: %d, File read/write error.", this, dcc7 ? dcc7->peer_uin : 0);
+ break;
+ case GG_ERROR_DCC7_EOF:
+ netlog("gg_mainthread(%x): Client: %d, End of file/connection error.", this, dcc7 ? dcc7->peer_uin : 0);
+ break;
+ case GG_ERROR_DCC7_REFUSED:
+ netlog("gg_mainthread(%x): Client: %d, Connection refused error.", this, dcc7 ? dcc7->peer_uin : 0);
+ break;
+ case GG_ERROR_DCC7_RELAY:
+ netlog("gg_mainthread(%x): Client: %d, Relay connection error.", this, dcc7 ? dcc7->peer_uin : 0);
+ break;
+ default:
+ netlog("gg_mainthread(%x): Client: %d, Unknown error.", this, dcc7 ? dcc7->peer_uin : 0);
+ }
+ if (!dcc7) break;
+
+ // Remove from watches
+ list_remove(&watches, dcc7, 0);
+
+ // Close file & fail
+ if (dcc7->file_fd != -1)
+ {
+ _close(dcc7->file_fd);
+ dcc7->file_fd = -1;
+ }
+
+ if (dcc7->contact)
+ ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc7, 0);
+
+ // Free dcc
+ gg_dcc7_free(dcc7);
+ }
+ break;
+
+ case GG_EVENT_XML_ACTION:
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS)) {
+ HXML hXml;
+ TCHAR *xmlAction;
+ TCHAR *tag;
+
+ xmlAction = mir_a2t(e->event.xml_action.data);
+ tag = mir_a2t("events");
+ hXml = xi.parseString(xmlAction, 0, tag);
+
+ if (hXml != NULL) {
+ HXML node;
+ char *type, *sender;
+
+ mir_free(tag);
+ tag = mir_a2t("event/type");
+ node = xi.getChildByPath(hXml, tag, 0);
+ type = node != NULL ? mir_t2a(xi.getText(node)) : NULL;
+
+ mir_free(tag);
+ tag = mir_a2t("event/sender");
+ node = xi.getChildByPath(hXml, tag, 0);
+ sender = node != NULL ? mir_t2a(xi.getText(node)) : NULL;
+ netlog("gg_mainthread(%x): XML Action type: %s.", this, type != NULL ? type : "unknown");
+ // Avatar change notify
+ if (type != NULL && !strcmp(type, "28")) {
+ netlog("gg_mainthread(%x): Client %s changed his avatar.", this, sender);
+ requestAvatar(getcontact(atoi(sender), 0, 0, NULL), 0);
+ }
+ mir_free(type);
+ mir_free(sender);
+ xi.destroyNode(hXml);
+ }
+ mir_free(tag);
+ mir_free(xmlAction);
+ }
+ break;
+
+ case GG_EVENT_TYPING_NOTIFICATION:
+ {
+ HANDLE hContact = getcontact(e->event.typing_notification.uin, 0, 0, NULL);
+#ifdef DEBUGMODE
+ netlog("gg_mainthread(%x): Typing notification from %d (%d).", this,
+ e->event.typing_notification.uin, e->event.typing_notification.length);
+#endif
+ CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact,
+ e->event.typing_notification.length > 0 ? 7 : PROTOTYPE_CONTACTTYPING_OFF);
+ }
+ break;
+ }
+ // Free event struct
+ gg_free_event(e);
+ }
+
+ broadcastnewstatus(ID_STATUS_OFFLINE);
+ setalloffline();
+ db_set_w(NULL, m_szModuleName, GG_KEY_LOGONTIME, 0);
+
+ // If it was unwanted disconnection reconnect
+ if (m_iDesiredStatus != ID_STATUS_OFFLINE
+ && db_get_b(NULL, m_szModuleName, GG_KEY_ARECONNECT, GG_KEYDEF_ARECONNECT))
+ {
+ netlog("gg_mainthread(%x): Unintentional disconnection detected. Going to reconnect...", this);
+ hostnum = 0;
+ broadcastnewstatus(ID_STATUS_CONNECTING);
+ mir_free(p.status_descr);
+ goto retry;
+ }
+
+ mir_free(p.password);
+ mir_free(p.status_descr);
+
+ // Destroy concurrent sessions list
+ {
+ list_t l;
+ EnterCriticalSection(&sessions_mutex);
+ for (l = sessions; l; l = l->next)
+ {
+ struct gg_multilogon_session* sess = (struct gg_multilogon_session*)l->data;
+ mir_free(sess->name);
+ mir_free(sess);
+ }
+ list_destroy(sessions, 0);
+ sessions = NULL;
+ LeaveCriticalSection(&sessions_mutex);
+ }
+
+ // Stop dcc server
+ pth_dcc.dwThreadId = 0;
+#ifdef DEBUGMODE
+ netlog("gg_mainthread(%x): Waiting until DCC Server Thread finished, if needed.", this);
+#endif
+ threadwait(&pth_dcc);
+
+ netlog("gg_mainthread(%x): Server Thread Ending", this);
+ return;
+}
+
+////////////////////////////////////////////////////////////
+// Change status function
+void GGPROTO::broadcastnewstatus(int newStatus)
+{
+ int oldStatus;
+
+ EnterCriticalSection(&modemsg_mutex);
+ oldStatus = m_iStatus;
+ if (oldStatus == newStatus)
+ {
+ LeaveCriticalSection(&modemsg_mutex);
+ return;
+ }
+ m_iStatus = newStatus;
+ LeaveCriticalSection(&modemsg_mutex);
+
+ ProtoBroadcastAck(m_szModuleName, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) oldStatus, newStatus);
+
+ netlog("gg_broadcastnewstatus(): Broadcast new status: %d.", newStatus);
+}
+
+////////////////////////////////////////////////////////////
+// When contact is deleted
+int GGPROTO::contactdeleted(WPARAM wParam, LPARAM lParam)
+{
+ HANDLE hContact = (HANDLE) wParam;
+ uin_t uin; int type;
+ DBVARIANT dbv;
+
+ uin = (uin_t)db_get_b(hContact, m_szModuleName, GG_KEY_UIN, 0);
+ type = db_get_b(hContact, m_szModuleName, "ChatRoom", 0);
+
+ // Terminate conference if contact is deleted
+ if (type && !db_get_s(hContact, m_szModuleName, "ChatRoomID", &dbv, DBVT_ASCIIZ) && gc_enabled)
+ {
+ GCDEST gcdest = {m_szModuleName, dbv.pszVal, GC_EVENT_CONTROL};
+ GCEVENT gcevent = {sizeof(GCEVENT), &gcdest};
+ GGGC *chat = gc_lookup(dbv.pszVal);
+
+ netlog("gg_gc_event(): Terminating chat %x, id %s from contact list...", chat, dbv.pszVal);
+ if (chat)
+ {
+ // Destroy chat entry
+ free(chat->recipients);
+ list_remove(&chats, chat, 1);
+ // Terminate chat window / shouldn't cascade entry is deleted
+ CallServiceSync(MS_GC_EVENT, SESSION_OFFLINE, (LPARAM)&gcevent);
+ CallServiceSync(MS_GC_EVENT, SESSION_TERMINATE, (LPARAM)&gcevent);
+ }
+
+ DBFreeVariant(&dbv);
+ return 0;
+ }
+
+ if (uin && isonline())
+ {
+ EnterCriticalSection(&sess_mutex);
+ gg_remove_notify_ex(sess, uin, GG_USER_NORMAL);
+ LeaveCriticalSection(&sess_mutex);
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////
+// When db settings changed
+
+int GGPROTO::dbsettingchanged(WPARAM wParam, LPARAM lParam)
+{
+ DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *) lParam;
+ HANDLE hContact = (HANDLE) wParam;
+ char *szProto = NULL;
+
+ // Check if the contact is NULL or we are not online
+ if (!isonline())
+ return 0;
+
+ // If contact has been blocked
+ if (!strcmp(cws->szModule, m_szModuleName) && !strcmp(cws->szSetting, GG_KEY_BLOCK))
+ {
+ notifyuser(hContact, 1);
+ return 0;
+ }
+
+ // Contact is being renamed
+ if (gc_enabled && !strcmp(cws->szModule, m_szModuleName) && !strcmp(cws->szSetting, GG_KEY_NICK)
+ && cws->value.pszVal)
+ {
+ // Groupchat window contact is being renamed
+ DBVARIANT dbv;
+ int type = db_get_b(hContact, m_szModuleName, "ChatRoom", 0);
+ if (type && !db_get_s(hContact, m_szModuleName, "ChatRoomID", &dbv, DBVT_ASCIIZ))
+ {
+ // Most important... check redundancy (fucking cascading)
+ static int cascade = 0;
+ if (!cascade && dbv.pszVal)
+ {
+ GCDEST gcdest = {m_szModuleName, dbv.pszVal, GC_EVENT_CHANGESESSIONAME};
+ GCEVENT gcevent = {sizeof(GCEVENT), &gcdest};
+ gcevent.pszText = cws->value.pszVal;
+ netlog("gg_dbsettingchanged(): Conference %s was renamed to %s.", dbv.pszVal, cws->value.pszVal);
+ // Mark cascading
+ /* FIXME */ cascade = 1;
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent);
+ /* FIXME */ cascade = 0;
+ }
+ DBFreeVariant(&dbv);
+ }
+ else
+ // Change contact name on all chats
+ gc_changenick(hContact, cws->value.pszVal);
+ }
+
+ // Contact list changes
+ if (!strcmp(cws->szModule, "CList"))
+ {
+ // If name changed... change nick
+ if (!strcmp(cws->szSetting, "MyHandle") && cws->value.type == DBVT_ASCIIZ && cws->value.pszVal)
+ db_set_s(hContact, m_szModuleName, GG_KEY_NICK, cws->value.pszVal);
+
+ // If not on list changed
+ if (!strcmp(cws->szSetting, "NotOnList"))
+ {
+ if (db_get_b(hContact, "CList", "Hidden", 0))
+ return 0;
+ // Notify user normally this time if added to the list permanently
+ if (cws->value.type == DBVT_DELETED || (cws->value.type == DBVT_BYTE && cws->value.bVal == 0))
+ notifyuser(hContact, 1);
+ }
+ }
+ return 0;
+}
+
+////////////////////////////////////////////////////////////
+// All users set offline
+
+void GGPROTO::setalloffline()
+{
+ netlog("gg_setalloffline(): Setting buddies offline");
+ db_set_w(NULL, m_szModuleName, GG_KEY_STATUS, ID_STATUS_OFFLINE);
+ HANDLE hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
+ while (hContact)
+ {
+ char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto != NULL && !strcmp(szProto, m_szModuleName))
+ {
+ db_set_w(hContact, m_szModuleName, GG_KEY_STATUS, ID_STATUS_OFFLINE);
+ // Clear IP and port settings
+ db_unset(hContact, m_szModuleName, GG_KEY_CLIENTIP);
+ db_unset(hContact, m_szModuleName, GG_KEY_CLIENTPORT);
+ // Delete status descr
+ db_unset(hContact, "CList", GG_KEY_STATUSDESCR);
+ }
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0);
+ }
+#ifdef DEBUGMODE
+ netlog("gg_setalloffline(): End");
+#endif
+}
+
+////////////////////////////////////////////////////////////
+// All users set offline
+
+void GGPROTO::notifyuser(HANDLE hContact, int refresh)
+{
+ uin_t uin;
+ if (!hContact) return;
+ if (isonline() && (uin = (uin_t)db_get_b(hContact, m_szModuleName, GG_KEY_UIN, 0)))
+ {
+ // Check if user should be invisible
+ // Or be blocked ?
+ if ((db_get_w(hContact, m_szModuleName, GG_KEY_APPARENT, (WORD) ID_STATUS_ONLINE) == ID_STATUS_OFFLINE) ||
+ db_get_b(hContact, "CList", "NotOnList", 0))
+ {
+ mir_cslock l(sess_mutex);
+ if (refresh) {
+ gg_remove_notify_ex(sess, uin, GG_USER_NORMAL);
+ gg_remove_notify_ex(sess, uin, GG_USER_BLOCKED);
+ }
+
+ gg_add_notify_ex(sess, uin, GG_USER_OFFLINE);
+ }
+ else if (db_get_b(hContact, m_szModuleName, GG_KEY_BLOCK, 0))
+ {
+ mir_cslock l(sess_mutex);
+ if (refresh)
+ gg_remove_notify_ex(sess, uin, GG_USER_OFFLINE);
+
+ gg_add_notify_ex(sess, uin, GG_USER_BLOCKED);
+ }
+ else {
+ mir_cslock l(sess_mutex);
+ if (refresh)
+ gg_remove_notify_ex(sess, uin, GG_USER_BLOCKED);
+
+ gg_add_notify_ex(sess, uin, GG_USER_NORMAL);
+ }
+ }
+}
+
+void GGPROTO::notifyall()
+{
+ HANDLE hContact;
+ char *szProto;
+ int count = 0, cc = 0;
+ uin_t *uins;
+ char *types;
+
+ netlog("gg_notifyall(): Subscribing notification to all users");
+ // Readup count
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
+ while (hContact)
+ {
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto != NULL && !strcmp(szProto, m_szModuleName)) count ++;
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0);
+ }
+
+ // Readup list
+ /* FIXME: If we have nothing on the list but we omit gg_notify_ex we have problem with receiving any contacts */
+ if (count == 0)
+ {
+ if (isonline())
+ {
+ EnterCriticalSection(&sess_mutex);
+ gg_notify_ex(sess, NULL, NULL, 0);
+ LeaveCriticalSection(&sess_mutex);
+ }
+ return;
+ }
+ uins = (uin_t*)calloc(sizeof(uin_t), count);
+ types = (char*)calloc(sizeof(char), count);
+
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
+ while (hContact && cc < count)
+ {
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto != NULL && !strcmp(szProto, m_szModuleName) && (uins[cc] = db_get_b(hContact, m_szModuleName, GG_KEY_UIN, 0)))
+ {
+ if ((db_get_w(hContact, m_szModuleName, GG_KEY_APPARENT, (WORD) ID_STATUS_ONLINE) == ID_STATUS_OFFLINE) ||
+ db_get_b(hContact, "CList", "NotOnList", 0))
+ types[cc] = GG_USER_OFFLINE;
+ else if (db_get_b(hContact, m_szModuleName, GG_KEY_BLOCK, 0))
+ types[cc] = GG_USER_BLOCKED;
+ else
+ types[cc] = GG_USER_NORMAL;
+ cc ++;
+ }
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0);
+ }
+ if (cc < count) count = cc;
+
+ // Send notification
+ if (isonline())
+ {
+ EnterCriticalSection(&sess_mutex);
+ gg_notify_ex(sess, uins, types, count);
+ LeaveCriticalSection(&sess_mutex);
+ }
+
+ // Free variables
+ free(uins); free(types);
+}
+
+////////////////////////////////////////////////////////////
+// Get contact by uin
+
+HANDLE GGPROTO::getcontact(uin_t uin, int create, int inlist, TCHAR *szNick)
+{
+ // Look for contact in DB
+ HANDLE hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
+ while (hContact) {
+ char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto != NULL && !strcmp(szProto, m_szModuleName)) {
+ if ((uin_t)db_get_b(hContact, m_szModuleName, GG_KEY_UIN, 0) == uin
+ && db_get_b(hContact, m_szModuleName, "ChatRoom", 0) == 0)
+ {
+ if (inlist) {
+ db_unset(hContact, "CList", "NotOnList");
+ db_unset(hContact, "CList", "Hidden");
+ }
+ return hContact;
+ }
+ }
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0);
+ }
+ if (!create) return NULL;
+
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_ADD, 0, 0);
+ if (!hContact) {
+ netlog("gg_getcontact(): Failed to create Gadu-Gadu contact %s", szNick);
+ return NULL;
+ }
+
+ if (CallService(MS_PROTO_ADDTOCONTACT, (WPARAM) hContact, (LPARAM) m_szModuleName) != 0) {
+ // For some reason we failed to register the protocol for this contact
+ CallService(MS_DB_CONTACT_DELETE, (WPARAM) hContact, 0);
+ netlog("Failed to register GG contact %d", uin);
+ return NULL;
+ }
+
+ netlog("gg_getcontact(): Added buddy: %d", uin);
+ if (!inlist)
+ db_set_b(hContact, "CList", "NotOnList", 1);
+
+ db_set_w(hContact, m_szModuleName, GG_KEY_UIN, (DWORD) uin);
+ db_set_w(hContact, m_szModuleName, GG_KEY_STATUS, ID_STATUS_OFFLINE);
+
+ // If nick specified use it
+ if (szNick)
+ db_set_ts(hContact, m_szModuleName, GG_KEY_NICK, szNick);
+ else if (isonline()) {
+ gg_pubdir50_t req;
+
+ // Search for that nick
+ if (req = gg_pubdir50_new(GG_PUBDIR50_SEARCH)) {
+ // Add uin and search it
+ gg_pubdir50_add(req, GG_PUBDIR50_UIN, ditoa(uin));
+ gg_pubdir50_seq_set(req, GG_SEQ_GETNICK);
+ EnterCriticalSection(&sess_mutex);
+ gg_pubdir50(sess, req);
+ LeaveCriticalSection(&sess_mutex);
+ gg_pubdir50_free(req);
+ db_set_s(hContact, m_szModuleName, GG_KEY_NICK, ditoa(uin));
+ netlog("gg_getcontact(): Search for nick on uin: %d", uin);
+ }
+ }
+
+ // Add to notify list and pull avatar for the new contact
+ if (isonline())
+ {
+ PROTO_AVATAR_INFORMATIONT pai = {0};
+
+ EnterCriticalSection(&sess_mutex);
+ gg_add_notify_ex(sess, uin, (char)(inlist ? GG_USER_NORMAL : GG_USER_OFFLINE));
+ LeaveCriticalSection(&sess_mutex);
+
+ pai.cbSize = sizeof(pai);
+ pai.hContact = hContact;
+ getavatarinfo((WPARAM)GAIF_FORCE, (LPARAM)&pai);
+
+ // Change status of the contact with our own UIN (if got yourself added to the contact list)
+ if (db_get_b(NULL, m_szModuleName, GG_KEY_UIN, 0) == uin) {
+ char *szMsg;
+ EnterCriticalSection(&modemsg_mutex);
+ szMsg = mir_strdup(getstatusmsg(m_iStatus));
+ LeaveCriticalSection(&modemsg_mutex);
+ changecontactstatus(uin, status_m2gg(m_iStatus, szMsg != NULL), szMsg, 0, 0, 0, 0);
+ mir_free(szMsg);
+ }
+ }
+
+ // TODO server side list & add buddy
+ return hContact;
+}
+
+////////////////////////////////////////////////////////////
+// Status conversion
+
+int GGPROTO::status_m2gg(int status, int descr)
+{
+ // check frends only
+ int mask = db_get_b(NULL, m_szModuleName, GG_KEY_FRIENDSONLY, GG_KEYDEF_FRIENDSONLY) ? GG_STATUS_FRIENDS_MASK : 0;
+
+ if (descr)
+ {
+ switch(status)
+ {
+ case ID_STATUS_OFFLINE: return GG_STATUS_NOT_AVAIL_DESCR | mask;
+ case ID_STATUS_ONLINE: return GG_STATUS_AVAIL_DESCR | mask;
+ case ID_STATUS_AWAY: return GG_STATUS_BUSY_DESCR | mask;
+ case ID_STATUS_DND: return GG_STATUS_DND_DESCR | mask;
+ case ID_STATUS_FREECHAT: return GG_STATUS_FFC_DESCR | mask;
+ case ID_STATUS_INVISIBLE: return GG_STATUS_INVISIBLE_DESCR | mask;
+ default: return GG_STATUS_BUSY_DESCR | mask;
+ }
+ }
+ else
+ {
+ switch(status)
+ {
+ case ID_STATUS_OFFLINE: return GG_STATUS_NOT_AVAIL | mask;
+ case ID_STATUS_ONLINE: return GG_STATUS_AVAIL | mask;
+ case ID_STATUS_AWAY: return GG_STATUS_BUSY | mask;
+ case ID_STATUS_DND: return GG_STATUS_DND | mask;
+ case ID_STATUS_FREECHAT: return GG_STATUS_FFC | mask;
+ case ID_STATUS_INVISIBLE: return GG_STATUS_INVISIBLE | mask;
+ default: return GG_STATUS_BUSY | mask;
+ }
+ }
+}
+
+int GGPROTO::status_gg2m(int status)
+{
+ // ignore additional flags
+ status = GG_S(status);
+
+ // when user has status description but is offline (show it invisible)
+ if (status == GG_STATUS_NOT_AVAIL_DESCR && db_get_b(NULL, m_szModuleName, GG_KEY_SHOWINVISIBLE, GG_KEYDEF_SHOWINVISIBLE))
+ return ID_STATUS_INVISIBLE;
+
+ // rest of cases
+ switch(status)
+ {
+ case GG_STATUS_NOT_AVAIL:
+ case GG_STATUS_NOT_AVAIL_DESCR:
+ return ID_STATUS_OFFLINE;
+
+ case GG_STATUS_AVAIL:
+ case GG_STATUS_AVAIL_DESCR:
+ return ID_STATUS_ONLINE;
+
+ case GG_STATUS_BUSY:
+ case GG_STATUS_BUSY_DESCR:
+ return ID_STATUS_AWAY;
+
+ case GG_STATUS_DND:
+ case GG_STATUS_DND_DESCR:
+ return ID_STATUS_DND;
+
+ case GG_STATUS_FFC:
+ case GG_STATUS_FFC_DESCR:
+ return ID_STATUS_FREECHAT;
+
+ case GG_STATUS_INVISIBLE:
+ case GG_STATUS_INVISIBLE_DESCR:
+ return ID_STATUS_INVISIBLE;
+
+ case GG_STATUS_BLOCKED:
+ return ID_STATUS_NA;
+
+ default:
+ return ID_STATUS_OFFLINE;
+ }
+}
+
+////////////////////////////////////////////////////////////
+// Called when contact status is changed
+
+void GGPROTO::changecontactstatus(uin_t uin, int status, const char *idescr, int time, uint32_t remote_ip, uint16_t remote_port, uint32_t version)
+{
+ HANDLE hContact = getcontact(uin, 0, 0, NULL);
+
+ // Check if contact is on list
+ if (!hContact) return;
+
+ // Write contact status
+ db_set_w(hContact, m_szModuleName, GG_KEY_STATUS, (WORD)status_gg2m(status));
+
+ // Check if there's description and if it's not empty
+ if (idescr && *idescr)
+ {
+ netlog("gg_changecontactstatus(): Saving for %d status descr \"%s\".", uin, idescr);
+ db_set_s(hContact, "CList", GG_KEY_STATUSDESCR, idescr);
+ }
+ else
+ // Remove status if there's nothing
+ db_unset(hContact, "CList", GG_KEY_STATUSDESCR);
+
+ // Store contact ip and port
+ if (remote_ip) db_set_w(hContact, m_szModuleName, GG_KEY_CLIENTIP, (DWORD) swap32(remote_ip));
+ if (remote_port) db_set_w(hContact, m_szModuleName, GG_KEY_CLIENTPORT, (WORD) remote_port);
+ if (version)
+ {
+ char sversion[48];
+ db_set_w(hContact, m_szModuleName, GG_KEY_CLIENTVERSION, (DWORD) version);
+ mir_snprintf(sversion, sizeof(sversion), "%sGadu-Gadu %s", (version & 0x00ffffff) > 0x2b ? "Nowe " : "", gg_version2string(version));
+ db_set_s(hContact, m_szModuleName, "MirVer", sversion);
+ }
+}
+
+////////////////////////////////////////////////////////////
+// Returns GG client version string from packet version
+const char *gg_version2string(int v)
+{
+ const char *pstr = "???";
+ v &= 0x00ffffff;
+ switch(v)
+ {
+ case 0x2e:
+ pstr = "8.0 build 8283"; break;
+ case 0x2d:
+ pstr = "8.0 build 4881"; break;
+ case 0x2b:
+ pstr = "< 8.0"; break;
+ case 0x2a:
+ pstr = "7.7 build 3315"; break;
+ case 0x29:
+ pstr = "7.6 build 1688"; break;
+ case 0x28:
+ pstr = "7.5 build 2201"; break;
+ case 0x27:
+ pstr = "7.0 build 22"; break;
+ case 0x26:
+ pstr = "7.0 build 20"; break;
+ case 0x25:
+ pstr = "7.0 build 1"; break;
+ case 0x24:
+ pstr = "6.1 (155) / 7.6 (1359)"; break;
+ case 0x22:
+ pstr = "6.0 build 140"; break;
+ case 0x21:
+ pstr = "6.0 build 133"; break;
+ case 0x20:
+ pstr = "6.0b"; break;
+ case 0x1e:
+ pstr = "5.7b build 121"; break;
+ case 0x1c:
+ pstr = "5.7b"; break;
+ case 0x1b:
+ pstr = "5.0.5"; break;
+ case 0x19:
+ pstr = "5.0.3"; break;
+ case 0x18:
+ pstr = "5.0.0-1"; break;
+ case 0x17:
+ pstr = "4.9.2"; break;
+ case 0x16:
+ pstr = "4.9.1"; break;
+ case 0x15:
+ pstr = "4.8.9"; break;
+ case 0x14:
+ pstr = "4.8.1-3"; break;
+ case 0x11:
+ pstr = "4.6.1-10"; break;
+ case 0x10:
+ pstr = "4.5.15-22"; break;
+ case 0x0f:
+ pstr = "4.5.12"; break;
+ case 0x0b:
+ pstr = "4.0.25-30"; break;
+ default:
+ if (v < 0x0b)
+ pstr = "< 4.0.25";
+ else if (v > 0x2e)
+ pstr = ">= 8.0";
+ break;
+ }
+ return pstr;
+}