text: /**********************************************************************
   * LeechCraft - modular cross-platform feature rich internet client.
   * Copyright (C) 2006-2014  Georg Rudoy
   *
   * Boost Software License - Version 1.0 - August 17th, 2003
   *
   * Permission is hereby granted, free of charge, to any person or organization
   * obtaining a copy of the software and accompanying documentation covered by
   * this license (the "Software") to use, reproduce, display, distribute,
   * execute, and transmit the Software, and to prepare derivative works of the
   * Software, and to permit third-parties to whom the Software is furnished to
   * do so, all subject to the following:
   *
   * The copyright notices in the Software and this entire statement, including
   * the above license grant, this restriction and the following disclaimer,
   * must be included in all copies of the Software, in whole or in part, and
   * all derivative works of the Software, unless such copies or derivative
   * works are solely in the form of machine-executable object code generated by
   * a source language processor.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
   * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
   * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
   * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   * DEALINGS IN THE SOFTWARE.
   **********************************************************************/
  
  #include "vkconnection.h"
  #include <QNetworkAccessManager>
  #include <QNetworkRequest>
  #include <QNetworkReply>
  #include <QTimer>
  #include <QtDebug>
  #include <util/svcauth/vkauthmanager.h>
  #include <util/sll/queuemanager.h>
  #include <util/sll/urloperator.h>
  #include <util/sll/parsejson.h>
  #include <util/sll/prelude.h>
  #include <util/sll/slotclosure.h>
  #include "longpollmanager.h"
  #include "logger.h"
  #include "xmlsettingsmanager.h"
  
  namespace LeechCraft
  {
  namespace Azoth
  {
  namespace Murm
  {
  	namespace
  	{
  		const QString UserFields { "first_name,last_name,nickname,photo,photo_big,sex,"
  				"bdate,city,country,timezone,contacts,education,online,online_mobile" };
  
  		QStringList GetPerms ()
  		{
  			QStringList result { "messages", "notifications", "friends", "status", "photos", "audio", "docs" };
  			if (XmlSettingsManager::Instance ().property ("RequireOffline").toBool ())
  				result << "offline";
  			return result;
  		}
  
  		const QString CurrentAPIVersion { "5.25" };
  	}
  
  	VkConnection::CommandException::CommandException (const QString& str)
  	: runtime_error { str.toUtf8 ().constData () }
  	{
  	}
  
  	VkConnection::RecoverableException::RecoverableException ()
  	: CommandException { "VK connection recoverable error" }
  	{
  	}
  
  	VkConnection::UnrecoverableException::UnrecoverableException (int code, const QString& msg)
  	: CommandException { "VK connection error code " + QString::number (code) + "; " + msg }
  	, Code_ { code }
  	, Msg_ { msg }
  	{
  	}
  
  	int VkConnection::UnrecoverableException::GetCode () const
  	{
  		return Code_;
  	}
  
  	const QString& VkConnection::UnrecoverableException::GetMessage () const
  	{
  		return Msg_;
  	}
  
  	VkConnection::VkConnection (const QString& name,
  			const QByteArray& cookies, ICoreProxy_ptr proxy, Logger& logger)
  	: AuthMgr_ (new Util::SvcAuth::VkAuthManager (name, "3778319",
  			GetPerms (),
  			cookies, proxy, nullptr, this))
  	, Proxy_ (proxy)
  	, Logger_ (logger)
  	, LastCookies_ (cookies)
  	, CallQueue_ (new Util::QueueManager (400))
  	, LPManager_ (new LongPollManager (this, proxy))
  	, MarkOnlineTimer_ (new QTimer (this))
  	{
  		Logger_ << "==========================================";
  		connect (AuthMgr_,
  				SIGNAL (cookiesChanged (QByteArray)),
  				this,
  				SLOT (saveCookies (QByteArray)));
  		connect (AuthMgr_,
  				SIGNAL (gotAuthKey (QString)),
  				this,
  				SLOT (callWithKey (QString)));
  
  		connect (LPManager_,
  				SIGNAL (listening ()),
  				this,
  				SLOT (handleListening ()));
  		connect (LPManager_,
  				SIGNAL (stopped ()),
  				this,
  				SLOT (handlePollStopped ()));
  		connect (LPManager_,
  				SIGNAL (pollError ()),
  				this,
  				SLOT (handlePollError ()));
  		connect (LPManager_,
  				SIGNAL (gotPollData (QVariantMap)),
  				this,
  				SLOT (handlePollData (QVariantMap)));
  
  		Dispatcher_ [1] = [this] (const QVariantList&) {};
  		Dispatcher_ [2] = [this] (const QVariantList&) {};
  		Dispatcher_ [3] = [this] (const QVariantList&) {};
  
  		Dispatcher_ [4] = [this] (const QVariantList& items) { HandleMessage (items); };
  		Dispatcher_ [8] = [this] (const QVariantList& items)
  			{ emit userStateChanged (items.value (1).toLongLong () * -1, true); };
  		Dispatcher_ [9] = [this] (const QVariantList& items)
  			{ emit userStateChanged (items.value (1).toLongLong () * -1, false); };
  		Dispatcher_ [51] = [this] (const QVariantList& items)
  			{ emit mucChanged (items.value (1).toLongLong ()); };
  		Dispatcher_ [61] = [this] (const QVariantList& items)
  			{ emit gotTypingNotification (items.value (1).toULongLong ()); };
  
  		// Stuff has been read, we don't care
  		Dispatcher_ [6] = [this] (const QVariantList&) {};
  		Dispatcher_ [7] = [this] (const QVariantList&) {};
  
  		Dispatcher_ [101] = [this] (const QVariantList&) {};	// unknown stuff
  
  		MarkOnlineTimer_->setInterval (12 * 60 * 1000);
  		connect (MarkOnlineTimer_,
  				SIGNAL (timeout ()),
  				this,
  				SLOT (markOnline ()));
  
  		XmlSettingsManager::Instance ().RegisterObject ("RequireOffline",
  				this, "handleScopeSettingsChanged");
  	}
  
  	const QByteArray& VkConnection::GetCookies () const
  	{
  		return LastCookies_;
  	}
  
  	void VkConnection::RerequestFriends ()
  	{
  		Logger_ (IHaveConsole::PacketDirection::Out) << "RerequestFriends";
  		PushFriendsRequest ();
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::SendMessage (qulonglong to, const QString& body,
  			std::function<void (qulonglong)> idSetter, Type type, const QStringList& attachments)
  	{
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([=] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/messages.send");
  
  				auto query = "access_token=" + QUrl::toPercentEncoding (key.toUtf8 ());
  				query += '&';
  				query += type == Type::Dialog ? "user_id" : "chat_id";
  				query += '=' + QByteArray::number (to);
  				query += "&type=1";
  				query += "&message=" + QUrl::toPercentEncoding (body, {}, "+");
  
  				if (!attachments.isEmpty ())
  					query += "&attachment=" + attachments.join (",");
  
  				for (auto i = params.begin (); i != params.end (); ++i)
  					query += "&" + QUrl::toPercentEncoding (i.key ()) +
  							"=" + QUrl::toPercentEncoding (i.value ());
  
  				QNetworkRequest req (url);
  				req.setHeader (QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
  				auto reply = nam->post (req, query);
  				Logger_ (IHaveConsole::PacketDirection::Out)
  						<< url
  						<< " : posting"
  						<< QString::fromUtf8 (query);
  				MsgReply2Setter_ [reply] = idSetter;
  				connect (reply,
  						SIGNAL (finished ()),
  						this,
  						SLOT (handleMessageSent ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::SendTyping (qulonglong to)
  	{
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([this, nam, to] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/messages.setActivity");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("user_id", QString::number (to))
  						("type", "typing");
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest (url));
  				connect (reply,
  						SIGNAL (finished ()),
  						reply,
  						SLOT (deleteLater ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	namespace
  	{
  		template<typename T>
  		QString CommaJoin (const QList<T>& ids)
  		{
  			QStringList converted;
  			for (auto id : ids)
  				converted << QString::number (id);
  			return converted.join (",");
  		}
  	}
  
  	void VkConnection::MarkAsRead (const QList<qulonglong>& ids)
  	{
  		const auto& joined = CommaJoin (ids);
  
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([this, nam, joined] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/messages.markAsRead");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("message_ids", joined);
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest (url));
  				connect (reply,
  						SIGNAL (finished ()),
  						reply,
  						SLOT (deleteLater ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::RequestGeoIds (const QList<int>& codes, GeoSetter_f setter, GeoIdType type)
  	{
  		if (codes.isEmpty ())
  			return;
  
  		const auto& joined = CommaJoin (codes);
  
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([=] (const QString& key, const UrlParams_t& params)
  			{
  				QString method;
  				QString paramName;
  				switch (type)
  				{
  				case GeoIdType::Country:
  					method = "Countries";
  					paramName = "country_ids";
  					break;
  				case GeoIdType::City:
  					method = "Cities";
  					paramName = "city_ids";
  					break;
  				}
  
  				QUrl url ("https://api.vk.com/method/database.get" + method + "ById");
  				Util::UrlOperator { url }
  						("access_token", key)
  						(paramName, joined);
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest (url));
  				CountryReply2Setter_ [reply] = setter;
  				connect (reply,
  						SIGNAL (finished ()),
  						this,
  						SLOT (handleCountriesFetched ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::GetUserInfo (const QList<qulonglong>& ids)
  	{
  		GetUserInfo (ids,
  				[this] (const QList<UserInfo>& ids) { emit gotUsers (ids); });
  	}
  
  	namespace
  	{
  		AppInfo UserMap2AppInfo (const QVariantMap& userMap)
  		{
  			return
  			{
  				userMap ["online_app"].toULongLong (),
  				userMap ["online_mobile"].toBool (),
  				{},
  				{}
  			};
  		}
  
  		AppInfo AppMap2AppInfo (const QVariantMap& appMap)
  		{
  			return
  			{
  				appMap ["id"].toULongLong (),
  				true,
  				appMap ["title"].toString (),
  				QUrl::fromEncoded (appMap ["icon_25"].toByteArray ())
  			};
  		}
  
  		UserInfo UserMap2Info (const QVariantMap& userMap)
  		{
  			QList<qulonglong> lists;
  			for (const auto& item : userMap ["lists"].toList ())
  				lists << item.toULongLong ();
  
  			auto dateString = userMap ["bdate"].toString ();
  			if (dateString.count ('.') == 1)
  				dateString += ".1800";
  
  			const auto& contacts = userMap ["contacts"].toMap ();
  
  			const auto& countryMap = userMap ["country"].toMap ();
  			const auto& cityMap = userMap ["city"].toMap ();
  
  			return
  			{
  				userMap ["id"].toULongLong (),
  
  				userMap ["first_name"].toString (),
  				userMap ["last_name"].toString (),
  				userMap ["nickname"].toString (),
  
  				QUrl (userMap ["photo"].toString ()),
  				QUrl (userMap ["photo_big"].toString ()),
  
  				userMap ["sex"].toInt (),
  
  				QDate::fromString (dateString, "d.M.yyyy"),
  
  				contacts ["home_phone"].toString (),
  				contacts ["mobile_phone"].toString (),
  
  				userMap ["timezone"].toInt (),
  
  				countryMap ["id"].toInt (),
  				countryMap ["title"].toString (),
  				cityMap ["id"].toInt (),
  				cityMap ["title"].toString (),
  
  				static_cast<bool> (userMap ["online"].toULongLong ()),
  
  				lists,
  
  				UserMap2AppInfo (userMap)
  			};
  		}
  
  		QList<UserInfo> ParseUsers (const QVariantList& usersList)
  		{
  			QList<UserInfo> users;
  
  			for (const auto& item : usersList)
  			{
  				const auto& userMap = item.toMap ();
  				if (userMap.contains ("deactivated"))
  					continue;
  
  				users << UserMap2Info (userMap);
  			}
  
  			return users;
  		}
  	}
  
  	void VkConnection::GetUserInfo (const QList<qulonglong>& ids,
  			const std::function<void (QList<UserInfo>)>& cont)
  	{
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		const auto& joined = CommaJoin (ids);
  		PreparedCalls_.push_back ([=] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/users.get");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("fields", UserFields);
  				if (!joined.isEmpty ())
  					Util::UrlOperator { url } ("user_ids", joined);
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest { url });
  				new Util::SlotClosure<Util::DeleteLaterPolicy>
  				{
  					[this, reply, cont]
  					{
  						if (!CheckFinishedReply (reply))
  							return;
  
  						const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  						Logger_ << "got users reply" << data;
  						try
  						{
  							CheckReplyData (data, reply);
  						}
  						catch (const CommandException&)
  						{
  							return;
  						}
  
  						cont (ParseUsers (data.toMap () ["response"].toList ()));
  					},
  					reply,
  					SIGNAL (finished ()),
  					reply
  				};
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::RequestUserAppId (qulonglong id)
  	{
  		const auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([=] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/users.get");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("user_ids", QString::number (id))
  						("fields", "online,online_mobile");
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest { url });
  				new Util::SlotClosure<Util::DeleteLaterPolicy>
  				{
  					[this, reply, id]
  					{
  						if (!CheckFinishedReply (reply))
  							return;
  
  						const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  						Logger_ << "got users app data" << data;
  						try
  						{
  							CheckReplyData (data, reply);
  						}
  						catch (const CommandException&)
  						{
  							return;
  						}
  
  						const auto& users = data.toMap () ["response"].toList ();
  						const auto appId = users.value (0).toMap () ["online_app"].toULongLong ();
  						const auto isMobile = users.value (0).toMap () ["online_mobile"].toBool ();
  						emit gotUserAppInfoStub (id, { appId, isMobile, {}, {} });
  					},
  					reply,
  					SIGNAL (finished ()),
  					reply
  				};
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::GetMessageInfo (qulonglong id, MessageInfoSetter_f setter)
  	{
  		GetMessageInfo (QString::number (id), setter);
  	}
  
  	void VkConnection::GetMessageInfo (const QString& idStr, MessageInfoSetter_f setter)
  	{
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([=] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/messages.getById");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("message_ids", idStr)
  						("photo_sizes", "1");
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest (url));
  				Reply2MessageSetter_ [reply] = setter;
  				connect (reply,
  						SIGNAL (finished ()),
  						this,
  						SLOT (handleMessageInfoFetched ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::GetAppInfo (qulonglong appId, const std::function<void (AppInfo)>& setter)
  	{
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([=] (const QString&, const UrlParams_t& params)
  			{
  				QUrl url { "https://api.vk.com/method/apps.get" };
  				Util::UrlOperator { url }
  					("app_id", QString::number (appId));
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest { url });
  				new Util::SlotClosure<Util::DeleteLaterPolicy>
  				{
  					[reply, setter]
  					{
  						reply->deleteLater ();
  
  						const auto& data = Util::ParseJson (reply, Q_FUNC_INFO).toMap ();
  						if (data.isEmpty ())
  							return;
  
  						setter (AppMap2AppInfo (data ["response"].toMap ()));
  					},
  					reply,
  					SIGNAL (finished ()),
  					reply
  				};
  				return reply;
  			});
  	}
  
  	void VkConnection::AddFriendList (const QString& name, const QList<qulonglong>& ids)
  	{
  		const auto& joined = CommaJoin (ids);
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([this, joined, name, nam] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/friends.addList");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("name", name)
  						("user_ids", joined);
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest (url));
  				Reply2ListName_ [reply] = name;
  				connect (reply,
  						SIGNAL (finished ()),
  						this,
  						SLOT (handleFriendListAdded ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::ModifyFriendList (const ListInfo& list, const QList<qulonglong>& newContents)
  	{
  		const auto& joined = CommaJoin (newContents);
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([this, joined, list, nam] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/friends.editList");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("list_id", QString::number (list.ID_))
  						("name", list.Name_)
  						("user_ids", joined);
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest (url));
  				connect (reply,
  						SIGNAL (finished ()),
  						reply,
  						SLOT (deleteLater ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::SetNRIList (const QList<qulonglong>& ids)
  	{
  		const auto& joined = CommaJoin (ids);
  		auto nam = Proxy_->GetNetworkAccessManager ();
  
  		PreparedCalls_.push_back ([this, joined, nam] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/storage.set");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("key", "non_roster_items")
  						("value", joined);
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest (url));
  				connect (reply,
  						SIGNAL (finished ()),
  						reply,
  						SLOT (deleteLater ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::CreateChat (const QString& title, const QList<qulonglong>& ids)
  	{
  		const auto& joined = CommaJoin (ids);
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([=] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/messages.createChat");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("title", title)
  						("user_ids", joined);
  
  				AddParams (url, params);
  
  				const auto& infos = Util::Map (ids, &UserInfo::FromID);
  				auto reply = nam->get (QNetworkRequest (url));
  				Reply2ChatInfo_ [reply] = { 0, title, infos };
  				connect (reply,
  						SIGNAL (finished ()),
  						this,
  						SLOT (handleChatCreated ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::RequestChatInfo (qulonglong id)
  	{
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([=] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/messages.getChat");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("chat_id", QString::number (id))
  						("fields", UserFields);
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest (url));
  				connect (reply,
  						SIGNAL (finished ()),
  						this,
  						SLOT (handleChatInfo ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::AddChatUser (qulonglong chat, qulonglong user)
  	{
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([=] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/messages.addChatUser");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("chat_id", QString::number (chat))
  						("user_id", QString::number (user));
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest (url));
  				connect (reply,
  						SIGNAL (finished ()),
  						reply,
  						SLOT (deleteLater ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::RemoveChatUser (qulonglong chat, qulonglong user)
  	{
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([=] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/messages.removeChatUser");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("chat_id", QString::number (chat))
  						("user_id", QString::number (user));
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest (url));
  				Reply2ChatRemoveInfo_ [reply] = ChatRemoveInfo { chat, user };
  				connect (reply,
  						SIGNAL (finished ()),
  						this,
  						SLOT (handleChatUserRemoved ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::SetChatTitle (qulonglong chat, const QString& title)
  	{
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([=] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/messages.editChat");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("chat_id", QString::number (chat))
  						("title", title);
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest (url));
  				connect (reply,
  						SIGNAL (finished ()),
  						reply,
  						SLOT (deleteLater ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::SetStatus (QString status)
  	{
  		if (status.isEmpty ())
  			status = Status_.StatusString_;
  
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([=] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/status.set");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("text", status);
  
  				AddParams (url, params);
  
  				auto reply = nam->get (QNetworkRequest (url));
  				connect (reply,
  						SIGNAL (finished ()),
  						reply,
  						SLOT (deleteLater ()));
  				return reply;
  			});
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::SetStatus (const EntryStatus& status, bool updateString)
  	{
  		Logger_ (IHaveConsole::PacketDirection::Out) << "setting status" << status.State_;
  		LPManager_->ForceServerRequery ();
  
  		Status_ = status;
  		if (Status_.State_ == SOffline)
  		{
  			LPManager_->Stop ();
  			return;
  		}
  
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([this, nam] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl lpUrl ("https://api.vk.com/method/users.get");
  				Util::UrlOperator { lpUrl }
  						("access_token", key)
  						("fields",
  								"first_name,last_name,nickname,photo,photo_big,sex,"
  								"bdate,city,country,timezone,contacts,education");
  				AddParams (lpUrl, params);
  				auto reply = nam->get (QNetworkRequest (lpUrl));
  				connect (reply,
  						SIGNAL (finished ()),
  						this,
  						SLOT (handleGotSelfInfo ()));
  				return reply;
  			});
  		PreparedCalls_.push_back ([this, nam] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl lpUrl ("https://api.vk.com/method/friends.getLists");
  				Util::UrlOperator { lpUrl } ("access_token", key);
  				AddParams (lpUrl, params);
  				auto reply = nam->get (QNetworkRequest (lpUrl));
  				connect (reply,
  						SIGNAL (finished ()),
  						this,
  						SLOT (handleGotFriendLists ()));
  				return reply;
  			});
  
  		if (updateString)
  			SetStatus (Status_.StatusString_);
  
  		AuthMgr_->GetAuthKey ();
  	}
  
  	EntryStatus VkConnection::GetStatus () const
  	{
  		return CurrentStatus_;
  	}
  
  	void VkConnection::SetMarkingOnlineEnabled (bool enabled)
  	{
  		Logger_ (IHaveConsole::PacketDirection::Out) << "SetMarkingOnlineEnabled" << enabled;
  		MarkingOnline_ = enabled;
  
  		if (enabled)
  		{
  			markOnline ();
  			MarkOnlineTimer_->start ();
  		}
  		else
  			MarkOnlineTimer_->stop ();
  	}
  
  	void VkConnection::QueueRequest (VkConnection::PreparedCall_f call)
  	{
  		PreparedCalls_ << call;
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::AddParams (QUrl& url, const UrlParams_t& params)
  	{
  		Util::UrlOperator op { url };
  		for (auto i = params.begin (); i != params.end (); ++i)
  			op (i.key (), i.value ());
  	}
  
  	void VkConnection::HandleCaptcha (const QString& cid, const QString& value)
  	{
  		if (!CaptchaId2Call_.contains (cid))
  			return;
  
  		auto call = CaptchaId2Call_.take (cid);
  		if (value.isEmpty ())
  			return;
  
  		call.ClearParams ();
  		call.AddParam ({ "captcha_sid", cid });
  		call.AddParam ({ "captcha_img", value });
  
  		PreparedCalls_.push_front (call);
  
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::HandleMessage (const QVariantList& items)
  	{
  		const auto& params = items.value (7).toMap ();
  		MessageInfo info
  		{
  			items.value (1).toULongLong (),
  			items.value (3).toULongLong (),
  			items.value (6).toString (),
  			MessageFlags { items.value (2).toInt () },
  			QDateTime::fromTime_t (items.value (4).toULongLong ()),
  			params
  		};
  
  		if (info.Params_.contains ("from"))
  		{
  			info.From_ -= 2000000000;
  			info.Flags_ |= MessageFlag::Chat;
  		}
  		else
  			info.Flags_ &= ~MessageFlag::Chat;
  
  		if (info.Params_.contains ("fwd"))
  		{
  			GetMessageInfo (info.ID_,
  					[this, info] (const FullMessageInfo& fullInfo)
  					{
  						emit gotMessage (fullInfo, info);
  					});
  			return;
  		}
  
  		emit gotMessage (info);
  	}
  
  	void VkConnection::PushFriendsRequest ()
  	{
  		auto nam = Proxy_->GetNetworkAccessManager ();
  
  		PreparedCalls_.push_back ([this, nam] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl friendsUrl ("https://api.vk.com/method/friends.get");
  				Util::UrlOperator { friendsUrl }
  						("access_token", key)
  						("fields", UserFields);
  				AddParams (friendsUrl, params);
  				auto reply = nam->get (QNetworkRequest (friendsUrl));
  				connect (reply,
  						SIGNAL (finished ()),
  						this,
  						SLOT (handleGotFriends ()));
  				return reply;
  			});
  
  		PreparedCalls_.push_back ([this, nam] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/storage.get");
  				Util::UrlOperator { url }
  						("access_token", key)
  						("key", "non_roster_items");
  				AddParams (url, params);
  				auto reply = nam->get (QNetworkRequest (url));
  				connect (reply,
  						SIGNAL (finished ()),
  						this,
  						SLOT (handleGotNRI ()));
  				return reply;
  			});
  	}
  
  	auto VkConnection::FindRunning (QNetworkReply *reply) const -> RunningCalls_t::const_iterator
  	{
  		return std::find_if (RunningCalls_.begin (), RunningCalls_.end (),
  				[reply] (decltype (RunningCalls_.at (0)) call) { return call.first == reply; });
  	}
  
  	auto VkConnection::FindRunning (QNetworkReply *reply) -> RunningCalls_t::iterator
  	{
  		return std::find_if (RunningCalls_.begin (), RunningCalls_.end (),
  				[reply] (decltype (RunningCalls_.at (0)) call) { return call.first == reply; });
  	}
  
  	void VkConnection::RescheduleRequest (QNetworkReply *reply)
  	{
  		const auto pos = FindRunning (reply);
  		if (pos != RunningCalls_.end ())
  			PreparedCalls_.push_front (pos->second);
  		else
  			qWarning () << Q_FUNC_INFO
  					<< "no running call found for the reply";
  	}
  
  	bool VkConnection::CheckFinishedReply (QNetworkReply *reply)
  	{
  		reply->deleteLater ();
  
  		if (reply->error () == QNetworkReply::NoError)
  		{
  			APIErrorCount_ = 0;
  			return true;
  		}
  
  		qWarning () << Q_FUNC_INFO
  				<< "reply error:"
  				<< reply->error ()
  				<< reply->errorString ();
  
  		RescheduleRequest (reply);
  
  		++APIErrorCount_;
  
  		if (!ShouldRerunPrepared_)
  		{
  			QTimer::singleShot (30000,
  					this,
  					SLOT (rerunPrepared ()));
  			ShouldRerunPrepared_ = true;
  		}
  
  		return false;
  	}
  
  	void VkConnection::CheckReplyData (const QVariant& mapVar, QNetworkReply *reply)
  	{
  		const auto& map = mapVar.toMap ();
  		if (!map.contains ("error"))
  			return;
  
  		const auto& errMap = map ["error"].toMap ();
  		const auto ec = errMap ["error_code"].toInt ();
  		const auto& errMsg = errMap ["error_msg"].toString ();
  
  		Logger_ << "got error:" << ec << errMsg;
  		Logger_ << errMap;
  
  		switch (ec)
  		{
  		case 5:
  			RescheduleRequest (reply);
  			reauth ();
  			throw RecoverableException {};
  		case 14:
  		{
  			const auto pos = FindRunning (reply);
  			if (pos == RunningCalls_.end ())
  			{
  				qWarning () << Q_FUNC_INFO
  						<< "non-running reply captcha";
  				break;
  			}
  
  			const auto& cid = errMap ["captcha_sid"].toString ();
  			const auto& img = errMap ["captcha_img"].toString ();
  
  			CaptchaId2Call_ [cid] = pos->second;
  
  			emit captchaNeeded (cid, QUrl::fromEncoded (img.toUtf8 ()));
  
  			throw RecoverableException {};
  		}
  		}
  
  		throw UnrecoverableException { ec, errMsg };
  	}
  
  	void VkConnection::reauth ()
  	{
  		Logger_ << "reauthing";
  		AuthMgr_->clearAuthData ();
  		LPManager_->ForceServerRequery ();
  		LPManager_->start ();
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::rerunPrepared ()
  	{
  		ShouldRerunPrepared_ = false;
  
  		if (!PreparedCalls_.isEmpty ())
  			AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::callWithKey (const QString& key)
  	{
  		while (!PreparedCalls_.isEmpty ())
  		{
  			auto f = PreparedCalls_.takeFirst ();
  			f.AddParam ({ "v", CurrentAPIVersion });
  			CallQueue_->Schedule ([this, f, key]
  					{
  						const auto reply = f (key);
  						Logger_ (IHaveConsole::PacketDirection::Out) << reply->request ().url ();
  						RunningCalls_.append ({ reply, f });
  
  						connect (reply,
  								SIGNAL (destroyed ()),
  								this,
  								SLOT (handleReplyDestroyed ()));
  					});
  		}
  	}
  
  	void VkConnection::handleReplyDestroyed ()
  	{
  		const auto reply = static_cast<QNetworkReply*> (sender ());
  		const auto pos = FindRunning (reply);
  
  		if (pos == RunningCalls_.end ())
  		{
  			qWarning () << Q_FUNC_INFO
  					<< "finished a non-running reply";
  			return;
  		}
  
  		RunningCalls_.erase (pos);
  	}
  
  	void VkConnection::markOnline ()
  	{
  		if (Status_.State_ == SOffline)
  			return;
  
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([this, nam] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl url ("https://api.vk.com/method/account.setOnline");
  				Util::UrlOperator { url } ("access_token", key);
  				AddParams (url, params);
  				auto reply = nam->get (QNetworkRequest (url));
  				connect (reply,
  						SIGNAL (finished ()),
  						reply,
  						SLOT (deleteLater ()));
  				return reply;
  			});
  		Logger_ (IHaveConsole::PacketDirection::Out) << "markOnline";
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::handleListening ()
  	{
  		Logger_ << "listening now";
  		CurrentStatus_ = Status_;
  		emit statusChanged (GetStatus ());
  
  		SetMarkingOnlineEnabled (MarkingOnline_);
  	}
  
  	void VkConnection::handlePollError ()
  	{
  		Logger_ << "poll error";
  		CurrentStatus_ = EntryStatus ();
  		emit statusChanged (GetStatus ());
  	}
  
  	void VkConnection::handlePollStopped ()
  	{
  		Logger_ << "poll stopped";
  		CurrentStatus_ = Status_;
  		emit statusChanged (GetStatus ());
  
  		emit stoppedPolling ();
  
  		MarkOnlineTimer_->stop ();
  	}
  
  	void VkConnection::handlePollData (const QVariantMap& rootMap)
  	{
  		Logger_ << "got poll data" << rootMap;
  		for (const auto& update : rootMap ["updates"].toList ())
  		{
  			const auto& parmList = update.toList ();
  			const auto code = parmList.value (0).toInt ();
  
  			if (!Dispatcher_.contains (code))
  				qWarning () << Q_FUNC_INFO
  						<< "unknown code"
  						<< code
  						<< parmList;
  			else
  				Dispatcher_ [code] (parmList);
  		}
  	}
  
  	void VkConnection::handleFriendListAdded ()
  	{
  		auto reply = qobject_cast<QNetworkReply*> (sender ());
  		if (!CheckFinishedReply (reply))
  			return;
  
  		const auto& name = Reply2ListName_.take (reply);
  
  		const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  		try
  		{
  			CheckReplyData (data, reply);
  		}
  		catch (const CommandException&)
  		{
  			return;
  		}
  
  		bool converted = false;
  		const auto id = data.toMap () ["response"].toMap () ["lid"].toULongLong (&converted);
  		if (!converted)
  		{
  			qWarning () << Q_FUNC_INFO
  					<< "unable to parse reply"
  					<< data;
  			return;
  		}
  
  		emit addedLists ({ { id, name } });
  	}
  
  	void VkConnection::handleGotSelfInfo ()
  	{
  		auto reply = qobject_cast<QNetworkReply*> (sender ());
  		if (!CheckFinishedReply (reply))
  			return;
  
  		const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  		try
  		{
  			CheckReplyData (data, reply);
  		}
  		catch (const CommandException&)
  		{
  			return;
  		}
  
  		const auto& list = data.toMap () ["response"].toList ();
  		const auto& selfMap = list.value (0).toMap ();
  		if (selfMap.isEmpty ())
  		{
  			qWarning () << Q_FUNC_INFO
  					<< "null self map";
  			return;
  		}
  		emit gotSelfInfo (UserMap2Info (selfMap));
  	}
  
  	void VkConnection::handleGotFriendLists ()
  	{
  		auto reply = qobject_cast<QNetworkReply*> (sender ());
  		if (!CheckFinishedReply (reply))
  			return;
  
  		const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  		try
  		{
  			CheckReplyData (data, reply);
  		}
  		catch (const CommandException&)
  		{
  			return;
  		}
  
  		QList<ListInfo> lists;
  		const auto& responseList = data.toMap () ["response"].toMap () ["items"].toList ();
  		for (const auto& item : responseList)
  		{
  			const auto& map = item.toMap ();
  			lists.append ({ map ["id"].toULongLong (), map ["name"].toString () });
  		}
  		emit gotLists (lists);
  
  		PushFriendsRequest ();
  		AuthMgr_->GetAuthKey ();
  	}
  
  	void VkConnection::handleGotFriends ()
  	{
  		auto reply = qobject_cast<QNetworkReply*> (sender ());
  		if (!CheckFinishedReply (reply))
  			return;
  
  		const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  		try
  		{
  			CheckReplyData (data, reply);
  		}
  		catch (const CommandException&)
  		{
  			return;
  		}
  
  		const auto& users = ParseUsers (data.toMap () ["response"].toMap () ["items"].toList ());
  		emit gotUsers (users);
  
  		auto nam = Proxy_->GetNetworkAccessManager ();
  		PreparedCalls_.push_back ([this, nam] (const QString& key, const UrlParams_t& params)
  			{
  				QUrl msgUrl ("https://api.vk.com/method/messages.get");
  				Util::UrlOperator { msgUrl }
  						("access_token", key)
  						("photo_sizes", "1");
  				AddParams (msgUrl, params);
  				auto reply = nam->get (QNetworkRequest (msgUrl));
  				connect (reply,
  						SIGNAL (finished ()),
  						this,
  						SLOT (handleGotUnreadMessages ()));
  				return reply;
  			});
  
  		LPManager_->start ();
  	}
  
  	void VkConnection::handleGotNRI ()
  	{
  		auto reply = qobject_cast<QNetworkReply*> (sender ());
  		if (!CheckFinishedReply (reply))
  			return;
  
  		const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  		try
  		{
  			CheckReplyData (data, reply);
  		}
  		catch (const CommandException&)
  		{
  			return;
  		}
  
  		const auto& str = data.toMap () ["response"].toString ();
  		QList<qulonglong> ids;
  		for (const auto& sub : str.split (",", QString::SkipEmptyParts))
  		{
  			bool ok = false;
  			const auto id = sub.toULongLong (&ok);
  			if (ok)
  				ids << id;
  		}
  
  		emit gotNRIList (ids);
  	}
  
  	namespace
  	{
  		PhotoInfo PhotoMap2Info (const QVariantMap& map)
  		{
  			QString bigSrc;
  			QSize bigSize;
  			QString thumbSrc;
  			QSize thumbSize;
  
  			QString currentBigType;
  			const QStringList bigTypes { "x", "y", "z", "w" };
  
  			for (const auto& elem : map ["sizes"].toList ())
  			{
  				const auto& eMap = elem.toMap ();
  
  				auto size = [&eMap]
  				{
  					return QSize (eMap ["width"].toInt (), eMap ["height"].toInt ());
  				};
  
  				const auto& type = eMap ["type"].toString ();
  				if (type == "m")
  				{
  					thumbSrc = eMap ["src"].toString ();
  					thumbSize = size ();
  				}
  				else if (bigTypes.indexOf (type) > bigTypes.indexOf (currentBigType))
  				{
  					currentBigType = type;
  					bigSrc = eMap ["src"].toString ();
  					bigSize = size ();
  				}
  			}
  
  			return
  			{
  				map ["owner_id"].toLongLong (),
  				map ["id"].toULongLong (),
  				map ["album_id"].toLongLong (),
  
  				thumbSrc,
  				thumbSize,
  				bigSrc,
  				bigSize,
  
  				map ["access_key"].toString ()
  			};
  		}
  
  		AudioInfo AudioMap2Info (const QVariantMap& map)
  		{
  			return
  			{
  				map ["owner_id"].toLongLong (),
  				map ["id"].toULongLong (),
  				map ["artist"].toString (),
  				map ["title"].toString (),
  				map ["duration"].toInt (),
  				map ["url"].toString ()
  			};
  		}
  
  		void HandleBasicMsgInfo (FullMessageInfo& info, const QVariantMap& wallMap)
  		{
  			info.OwnerID_ = wallMap.contains ("owner_id") ?
  					wallMap ["owner_id"].toLongLong () :
  					wallMap ["to_id"].toLongLong ();
  			info.ID_ = wallMap ["id"].toULongLong ();
  			info.Text_ = wallMap.contains ("text") ?
  					wallMap ["text"].toString () :
  					wallMap ["body"].toString ();
  			info.Likes_ = wallMap ["likes"].toMap () ["count"].toInt ();
  			info.Reposts_ = wallMap ["reposts"].toMap () ["count"].toInt ();
  			info.PostDate_ = QDateTime::fromTime_t (wallMap ["date"].toLongLong ());
  		}
  
  		VideoInfo VideoMap2Info (const QVariantMap& map)
  		{
  			return
  			{
  				map ["owner_id"].toLongLong (),
  				map ["id"].toULongLong (),
  				map ["access_key"].toString (),
  				map ["title"].toString (),
  				map ["description"].toString (),
  				map ["duration"].toULongLong (),
  				map ["views"].toLongLong (),
  				map ["photo_320"].toString ()
  			};
  		}
  
  		DocumentInfo DocMap2Info (const QVariantMap& map)
  		{
  			return
  			{
  				map ["owner_id"].toLongLong (),
  				map ["id"].toULongLong (),
  				map ["title"].toString (),
  				map ["ext"].toString (),
  				map ["size"].toULongLong (),
  				map ["url"].toString ()
  			};
  		}
  
  		GiftInfo GiftMap2Info (const QVariantMap& map)
  		{
  			return
  			{
  				map ["id"].toULongLong (),
  				QUrl::fromEncoded (map ["thumb_256"].toByteArray ())
  			};
  		}
  
  		StickerInfo StickerMap2Info (const QVariantMap& map)
  		{
  			return
  			{
  				map ["id"].toString ()
  			};
  		}
  
  		PagePreview PagePreviewMap2Info (const QVariantMap& map)
  		{
  			return
  			{
  				map ["gid"].toLongLong (),
  				map ["pid"].toULongLong (),
  				map ["title"].toString (),
  				map ["view_url"].toString ()
  			};
  		}
  
  		void HandleAttachments (FullMessageInfo& info, const QVariant& attachments, Logger& logger)
  		{
  			const auto& attList = attachments.toList ();
  			for (const auto& attVar : attList)
  			{
  				const auto& attMap = attVar.toMap ();
  				if (attMap.contains ("photo"))
  					info.Photos_ << PhotoMap2Info (attMap ["photo"].toMap ());
  				else if (attMap.contains ("audio"))
  					info.Audios_ << AudioMap2Info (attMap ["audio"].toMap ());
  				else if (attMap.contains ("video"))
  					info.Videos_ << VideoMap2Info (attMap ["video"].toMap ());
  				else if (attMap.contains ("doc"))
  					info.Documents_ << DocMap2Info (attMap ["doc"].toMap ());
  				else if (attMap.contains ("gift"))
  					info.Gifts_ << GiftMap2Info (attMap ["gift"].toMap ());
  				else if (attMap.contains ("sticker"))
  					info.Stickers_ << StickerMap2Info (attMap ["sticker"].toMap ());
  				else if (attMap.contains ("page"))
  					info.PagesPreviews_ << PagePreviewMap2Info (attMap ["page"].toMap ());
  				else if (attMap.contains ("wall"))
  				{
  					auto wallMap = attMap ["wall"].toMap ();
  
  					FullMessageInfo repost;
  					HandleBasicMsgInfo (repost, wallMap);
  
  					if (wallMap.contains ("attachments"))
  					{
  						HandleAttachments (repost, wallMap.take ("attachments"), logger);
  						logger << "attachments left:" << wallMap;
  					}
  					if (wallMap.contains ("copy_history"))
  					{
  						auto history = wallMap.take ("copy_history");
  						for (const auto& obj : history.toList ())
  							HandleAttachments (repost, obj.toMap () ["attachments"], logger);
  					}
  
  					info.ContainedReposts_.append (repost);
  				}
  				else
  					logger << "HandleAttachments" << attMap.keys ();
  			}
  		}
  
  		FullMessageInfo GetFullMessageInfo (const QVariantMap& map, Logger& logger);
  
  		void HandleForwarded (FullMessageInfo& info, const QVariant& fwds, Logger& logger)
  		{
  			const auto& fwdList = fwds.toList ();
  			for (const auto& fwdVar : fwdList)
  				info.ForwardedMessages_ << GetFullMessageInfo (fwdVar.toMap (), logger);
  		}
  
  		FullMessageInfo GetFullMessageInfo (const QVariantMap& map, Logger& logger)
  		{
  			FullMessageInfo info;
  			HandleBasicMsgInfo (info, map);
  			HandleAttachments (info, map ["attachments"], logger);
  			HandleForwarded (info, map ["fwd_messages"], logger);
  			return info;
  		}
  	}
  
  	void VkConnection::handleGotUnreadMessages ()
  	{
  		auto reply = qobject_cast<QNetworkReply*> (sender ());
  		if (!CheckFinishedReply (reply))
  			return;
  
  		const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  		try
  		{
  			CheckReplyData (data, reply);
  		}
  		catch (const CommandException&)
  		{
  			return;
  		}
  
  		auto respList = data.toMap () ["response"].toMap () ["items"].toList ();
  		if (respList.isEmpty ())
  		{
  			qWarning () << Q_FUNC_INFO
  					<< "no response"
  					<< data;
  			return;
  		}
  		respList.removeFirst ();
  
  		QList<QPair<MessageInfo, FullMessageInfo>> infos;
  		for (const auto& msgMapVar : respList)
  		{
  			const auto& map = msgMapVar.toMap ();
  			if (map ["read_state"].toULongLong ())
  				continue;
  
  			Logger_ << "got unread message:" << QVariant { map };
  
  			MessageFlags flags = MessageFlag::Unread;
  			if (map ["out"].toULongLong ())
  				flags |= MessageFlag::Outbox;
  
  			MessageInfo info
  			{
  				map ["id"].toULongLong (),
  				map ["user_id"].toULongLong (),
  				map ["body"].toString (),
  				flags,
  				QDateTime::fromTime_t (map ["date"].toULongLong ()),
  				{}
  			};
  
  			if (map.contains ("chat_id"))
  			{
  				info.Flags_ |= MessageFlag::Chat;
  				info.Params_ ["from"] = info.From_;
  				info.From_ = map ["chat_id"].toULongLong ();
  			}
  
  			infos.append ({ info, GetFullMessageInfo (map, Logger_) });
  		}
  
  		std::sort (infos.begin (), infos.end (),
  				[] (const QPair<MessageInfo, FullMessageInfo>& m1, const QPair<MessageInfo, FullMessageInfo>& m2)
  					{ return m1.first.TS_ < m2.first.TS_; });
  		for (const auto& pair : infos)
  			emit gotMessage (pair.second, pair.first);
  	}
  
  	void VkConnection::handleChatCreated ()
  	{
  		auto reply = qobject_cast<QNetworkReply*> (sender ());
  		if (!CheckFinishedReply (reply))
  			return;
  
  		auto info = Reply2ChatInfo_.take (reply);
  
  		const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  		try
  		{
  			CheckReplyData (data, reply);
  		}
  		catch (const CommandException&)
  		{
  			return;
  		}
  
  		info.ChatID_ = data.toMap () ["response"].toULongLong ();
  
  		emit gotChatInfo (info);
  	}
  
  	void VkConnection::handleChatInfo ()
  	{
  		auto reply = qobject_cast<QNetworkReply*> (sender ());
  		if (!CheckFinishedReply (reply))
  			return;
  
  		const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  		try
  		{
  			CheckReplyData (data, reply);
  		}
  		catch (const CommandException&)
  		{
  			return;
  		}
  
  		const auto& map = data.toMap () ["response"].toMap ();
  		emit gotChatInfo ({
  				map ["id"].toULongLong (),
  				map ["title"].toString (),
  				ParseUsers (map ["users"].toList ())
  			});
  	}
  
  	void VkConnection::handleChatUserRemoved ()
  	{
  		auto reply = qobject_cast<QNetworkReply*> (sender ());
  		if (!CheckFinishedReply (reply))
  			return;
  
  		auto removeInfo = Reply2ChatRemoveInfo_.take (reply);
  
  		const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  		try
  		{
  			CheckReplyData (data, reply);
  		}
  		catch (const CommandException&)
  		{
  			return;
  		}
  
  		const auto& map = data.toMap ();
  		if (map ["response"].toULongLong () == 1)
  			emit chatUserRemoved (removeInfo.Chat_, removeInfo.User_);
  	}
  
  	void VkConnection::handleMessageSent ()
  	{
  		auto reply = qobject_cast<QNetworkReply*> (sender ());
  		const auto& setter = MsgReply2Setter_.take (reply);
  		if (!setter)
  			return;
  
  		if (!CheckFinishedReply (reply))
  			return;
  
  		const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  		try
  		{
  			CheckReplyData (data, reply);
  		}
  		catch (const CommandException&)
  		{
  			return;
  		}
  
  		const auto code = data.toMap ().value ("response", -1).toULongLong ();
  		setter (code);
  	}
  
  	void VkConnection::handleCountriesFetched ()
  	{
  		auto reply = qobject_cast<QNetworkReply*> (sender ());
  		const auto& setter = CountryReply2Setter_.take (reply);
  		if (!setter)
  			return;
  
  		if (!CheckFinishedReply (reply))
  			return;
  
  		const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  		try
  		{
  			CheckReplyData (data, reply);
  		}
  		catch (const CommandException&)
  		{
  			return;
  		}
  
  		QHash<int, QString> result;
  		for (const auto& item : data.toMap () ["response"].toList ())
  		{
  			const auto& map = item.toMap ();
  			result [map ["id"].toInt ()] = map ["title"].toString ();
  		}
  
  		setter (result);
  	}
  
  	void VkConnection::handleMessageInfoFetched ()
  	{
  		auto reply = qobject_cast<QNetworkReply*> (sender ());
  		const auto& setter = Reply2MessageSetter_.take (reply);
  		if (!setter)
  			return;
  
  		if (!CheckFinishedReply (reply))
  			return;
  
  		const auto& data = Util::ParseJson (reply, Q_FUNC_INFO);
  		try
  		{
  			CheckReplyData (data, reply);
  		}
  		catch (const CommandException&)
  		{
  			return;
  		}
  
  		Logger_ << "got message info data" << data;
  
  		FullMessageInfo info;
  		const auto& infoList = data.toMap () ["response"].toMap () ["items"].toList ();
  		for (const auto& item : infoList)
  		{
  			if (item.type () != QVariant::Map)
  				continue;
  
  			info = GetFullMessageInfo (item.toMap (), Logger_);
  		}
  
  		setter (info);
  	}
  
  	void VkConnection::handleScopeSettingsChanged ()
  	{
  		AuthMgr_->UpdateScope (GetPerms ());
  	}
  
  	void VkConnection::saveCookies (const QByteArray& cookies)
  	{
  		LastCookies_ = cookies;
  		emit cookiesChanged ();
  	}
  }
  }
  }
  