/*
Copyright (c) 2013-14 Miranda NG project (http://miranda-ng.org)
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 version 2
of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "stdafx.h"
static char* szImageTypes[] = { "photo_2560", "photo_1280", "photo_807", "photo_604", "photo_256", "photo_130", "photo_128", "photo_75", "photo_64" };
void CVkProto::AddFeedSpecialUser()
{
bool bSpecialContact = m_bNewsEnabled || m_bNotificationsEnabled || m_bSpecialContactAlwaysEnabled;
MCONTACT hContact = FindUser(VK_FEED_USER);
if (!bSpecialContact) {
if (hContact)
CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0);
return;
}
if (!hContact) {
hContact = FindUser(VK_FEED_USER, true);
CMString tszNick = TranslateT("VKontakte");
setTString(hContact, "Nick", tszNick.GetBuffer());
CMString tszUrl = _T("https://vk.com/press/Simple.png");
SetAvatarUrl(hContact, tszUrl);
ReloadAvatarInfo(hContact);
setTString(hContact, "domain", _T("feed"));
setTString(hContact, "Homepage", _T("https://vk.com/feed"));
}
if (getWord(hContact, "Status", 0) != ID_STATUS_ONLINE)
setWord(hContact, "Status", ID_STATUS_ONLINE);
SetMirVer(hContact, 7);
}
void CVkProto::AddFeedEvent(CMString& tszBody, time_t tTime)
{
MCONTACT hContact = FindUser(VK_FEED_USER, true);
ptrT ptszBody(mir_tstrdup(tszBody.GetBuffer()));
PROTORECVEVENT recv = { 0 };
recv.flags = PREF_TCHAR;
recv.timestamp = tTime;
recv.tszMessage = ptszBody;
recv.lParam = 0;
recv.pCustomData = NULL;
recv.cbCustomDataSize = 0;
ProtoChainRecvMsg(hContact, &recv);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
CVkUserInfo* CVkProto::GetVkUserInfo(LONG iUserId, OBJLIST &vkUsers)
{
debugLogA("CVkProto::GetVkUserInfo %d", iUserId);
if (iUserId == 0)
return NULL;
bool bIsGroup = (iUserId < 0);
CVkUserInfo * vkUser = vkUsers.find((CVkUserInfo *)&iUserId);
if (vkUser == NULL) {
CMString tszNick = TranslateT("Unknown");
CMString tszLink = _T("https://vk.com/");
if (iUserId){
tszLink += bIsGroup ? "club" : "id";
tszLink.AppendFormat(_T("%d"), bIsGroup ? -iUserId : iUserId);
}
vkUser = new CVkUserInfo(iUserId, bIsGroup, tszNick, tszLink, bIsGroup ? NULL : FindUser(iUserId));
vkUsers.insert(vkUser);
}
return vkUser;
}
void CVkProto::CreateVkUserInfoList(OBJLIST &vkUsers, JSONNODE *pResponse)
{
debugLogA("CVkProto::CreateVkUserInfoList");
if (pResponse == NULL)
return;
JSONNODE *pProfiles = json_get(pResponse, "profiles");
JSONNODE *pProfile;
if (pProfiles != NULL)
for (size_t i = 0; (pProfile = json_at(pProfiles, i)) != NULL; i++){
LONG UserId = json_as_int(json_get(pProfile, "id"));
if (!UserId)
continue;
CMString tszNick = json_as_string(json_get(pProfile, "first_name"));
tszNick.AppendChar(' ');
tszNick += json_as_string(json_get(pProfile, "last_name"));
CMString tszLink = _T("https://vk.com/");
tszLink += json_as_string(json_get(pProfile, "screen_name"));
CVkUserInfo * vkUser = new CVkUserInfo(UserId, false, tszNick, tszLink, FindUser(UserId));
vkUsers.insert(vkUser);
}
JSONNODE *pGroups = json_get(pResponse, "groups");
if (pGroups != NULL)
for (size_t i = 0; (pProfile = json_at(pGroups, i)) != NULL; i++){
LONG UserId = -json_as_int(json_get(pProfile, "id"));
if (!UserId)
continue;
CMString tszNick = json_as_string(json_get(pProfile, "name"));
CMString tszLink = _T("https://vk.com/");
tszLink += json_as_string(json_get(pProfile, "screen_name"));
CVkUserInfo * vkUser = new CVkUserInfo(UserId, true, tszNick, tszLink);
vkUsers.insert(vkUser);
}
}
CMString CVkProto::GetVkPhotoItem(JSONNODE *pPhoto)
{
CMString tszRes;
if (pPhoto == NULL)
return tszRes;
ptrT ptszLink;
for (int i = 0; i < SIZEOF(szImageTypes); i++) {
JSONNODE *n = json_get(pPhoto, szImageTypes[i]);
if (n != NULL) {
ptszLink = json_as_string(n);
break;
}
}
int iWidth = json_as_int(json_get(pPhoto, "width"));
int iHeight = json_as_int(json_get(pPhoto, "height"));
tszRes.AppendFormat(_T("%s: %s (%dx%d)"), TranslateT("Photo"), ptszLink ? ptszLink : _T(""), iWidth, iHeight);
if (m_bAddImgBbc)
tszRes.AppendFormat(_T("\n\t[img]%s[/img]"), ptszLink);
CMString tszText = json_as_string(json_get(pPhoto, "text"));
if (!tszText.IsEmpty())
tszRes += "\n" + tszText;
return tszRes;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
CMString CVkProto::GetVkNewsItem(JSONNODE *pItem, OBJLIST &vkUsers, time_t &tDate)
{
debugLogA("CVkProto::GetVkNewsItem");
bool bPostLink = true;
CMString tszRes;
if (pItem == NULL)
return tszRes;
CMString tszType = json_as_string(json_get(pItem, "type"));
LONG iSourceId = json_as_int(json_get(pItem, "source_id"));
iSourceId = iSourceId ? iSourceId : json_as_int(json_get(pItem, "owner_id"));
CVkUserInfo *vkUser = GetVkUserInfo(iSourceId, vkUsers);
LONG iPostId = json_as_int(json_get(pItem, "post_id"));
tDate = json_as_int(json_get(pItem, "date"));
CMString tszText = json_as_string(json_get(pItem, "text"));
if (!tszText.IsEmpty())
tszText += _T("\n");
if (tszType == _T("photo_tag")){
bPostLink = false;
JSONNODE *pPhotos = json_get(pItem, "photo_tags");
if (pPhotos){
JSONNODE *pPhotoItems = json_get(pPhotos, "items");
if (pPhotoItems){
JSONNODE *pPhotoItem;
tszText = TranslateT("User was tagged in these photos:");
for (size_t i = 0; (pPhotoItem = json_at(pPhotoItems, i)) != NULL; i++)
tszText += _T("\n") + GetVkPhotoItem(pPhotoItem);
}
}
}
else if (tszType == _T("photo") || tszType == _T("wall_photo")){
bPostLink = false;
JSONNODE *pPhotos = json_get(pItem, "photos");
if (pPhotos){
JSONNODE *pPhotoItems = json_get(pPhotos, "items"), *pPhotoItem;
if (pPhotoItems)
for (size_t i = 0; (pPhotoItem = json_at(pPhotoItems, i)) != NULL; i++){
tszText += GetVkPhotoItem(pPhotoItem) + _T("\n");
if (i == 0 && tszType == _T("wall_photo")){
iPostId = json_as_int(json_get(pPhotoItem, "post_id"));
bPostLink = true;
break; // Max 1 wall_photo
}
}
}
}
else if (tszType == _T("post") || tszType.IsEmpty()) {
bPostLink = true;
JSONNODE * pRepost = json_get(pItem, "copy_history");
if (pRepost){
time_t tRDate;
CMString tszRepostText = GetVkNewsItem(json_at(pRepost, 0), vkUsers, tRDate);
tszRepostText.Replace(_T("\n"), _T("\n\t"));
tszText += tszRepostText;
tszText += _T("\n");
}
JSONNODE *pAttachments = json_get(pItem, "attachments");
if (pAttachments)
tszText += GetAttachmentDescr(pAttachments);
}
CMString tszResFormat;
CMString tszUrl;
CMString tszBBCIn = m_bBBCOnNews ? _T("[b]") : _T("");
CMString tszBBCOut = m_bBBCOnNews ? _T("[/b]") : _T("");
if (iPostId)
tszResFormat = Translate("News from %s%s%s (%s)\n%s");
else {
tszResFormat = Translate("\tRepost from %s%s%s (%s)\n%s");
bPostLink = false;
}
tszRes.AppendFormat(tszResFormat, tszBBCIn.GetBuffer(), vkUser->m_tszUserNick.GetBuffer(), tszBBCOut.GetBuffer(), vkUser->m_tszLink.GetBuffer(), tszText.GetBuffer());
if (bPostLink) {
tszUrl.AppendFormat(_T("%d_%d"), vkUser->m_UserId, iPostId);
tszUrl = CMString(_T("https://vk.com/wall")) + tszUrl;
tszRes.AppendFormat(TranslateT("\nNews link: %s"), tszUrl.GetBuffer());
}
return tszRes;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
CMString CVkProto::GetVkFeedback(JSONNODE *pFeedback, VKObjType vkFeedbackType, OBJLIST &vkUsers, CVkUserInfo *vkUser)
{
debugLogA("CVkProto::GetVkFeedback");
CMString tszRes;
if (!pFeedback || !vkFeedbackType)
return tszRes;
CMString tszBBCIn = m_bBBCOnNews ? _T("[b]") : _T("");
CMString tszBBCOut = m_bBBCOnNews ? _T("[/b]") : _T("");
CMString tszFormat;
LONG iUserId = 0;
if (vkFeedbackType == vkComment) {
iUserId = json_as_int(json_get(pFeedback, "from_id"));
tszFormat = _T("%s%s%s (%s) %%s %%s\n%s");
}
else if (vkFeedbackType == vkPost) {
iUserId = json_as_int(json_get(pFeedback, "owner_id "));
tszFormat = _T("%s%s%s (%s) %%s %%s\n%s");
}
else if (vkFeedbackType == VKObjType::vkUsers || vkFeedbackType == vkCopy){
JSONNODE *pUsers = json_get(pFeedback, "items"), *pUserItem;
CMString tszUsers;
for (int i = 0; (pUserItem = json_at(pUsers, i)) != NULL; i++){
iUserId = json_as_int(json_get(pUserItem, "from_id"));
if (iUserId == 0)
continue;
vkUser = GetVkUserInfo(iUserId, vkUsers);
if (!tszUsers.IsEmpty())
tszUsers += _T(", ");
tszUsers.AppendFormat(_T("%s%s%s (%s)"), tszBBCIn.GetBuffer(), vkUser->m_tszUserNick.GetBuffer(), tszBBCOut.GetBuffer(), vkUser->m_tszLink.GetBuffer());
}
tszRes.AppendFormat(_T("%s %%s %%s"), tszUsers.GetBuffer());
vkUser = NULL;
iUserId = 0;
}
if (iUserId){
vkUser = GetVkUserInfo(iUserId, vkUsers);
CMString tszText = json_as_string(json_get(pFeedback, "text"));
size_t iNameEnd = tszText.Find(_T("],")), iNameBeg = tszText.Find(_T("|"));
if (iNameEnd != -1 && iNameBeg != -1 && iNameBeg < iNameEnd){
CMString tszName = tszText.Mid(iNameBeg + 1, iNameEnd - iNameBeg - 1);
CMString tszBody = tszText.Mid(iNameEnd + 2);
if (!tszName.IsEmpty() && !tszBody.IsEmpty())
tszText = tszName + _T(",") + tszBody;
}
tszRes.AppendFormat(tszFormat, tszBBCIn.GetBuffer(), vkUser->m_tszUserNick.GetBuffer(), tszBBCOut.GetBuffer(), vkUser->m_tszLink.GetBuffer(), tszText.GetBuffer());
}
return tszRes;
}
CMString CVkProto::GetVkParent(JSONNODE *pParent, VKObjType vkParentType)
{
debugLogA("CVkProto::GetVkParent");
CMString tszRes;
if (!pParent || !vkParentType)
return tszRes;
CMString tszBBCIn = m_bBBCOnNews ? _T("[b]") : _T("");
CMString tszBBCOut = m_bBBCOnNews ? _T("[/b]") : _T("");
if (vkParentType == vkPhoto) {
CMString tszPhoto = GetVkPhotoItem(pParent);
LONG iOwnerId = json_as_int(json_get(pParent, "owner_id"));
LONG iId = json_as_int(json_get(pParent, "id"));
CMString tszFormat = _T("\n%s\n%s%s%s: https://vk.com/photo%d_%d");
tszRes.AppendFormat(tszFormat, tszPhoto.GetBuffer(), tszBBCIn.GetBuffer(), TranslateT("Link"), tszBBCOut.GetBuffer(), iOwnerId, iId);
}
else if (vkParentType == vkVideo) {
LONG iOwnerId = json_as_int(json_get(pParent, "owner_id"));
LONG iId = json_as_int(json_get(pParent, "id"));
CMString tszTitle = json_as_string(json_get(pParent, "title"));
CMString tszFormat = _T("\n%s%s%s\n%s%s%s: https://vk.com/video%d_%d");
tszRes.AppendFormat(tszFormat, tszBBCIn.GetBuffer(), tszTitle.GetBuffer(), tszBBCOut.GetBuffer(), tszBBCIn.GetBuffer(), TranslateT("Link"), tszBBCOut.GetBuffer(), iOwnerId, iId);
}
else if (vkParentType == vkPost) {
LONG iOwnerId = json_as_int(json_get(pParent, "from_id"));
LONG iId = json_as_int(json_get(pParent, "id"));
CMString tszFormat = _T("\n%s%s%s: https://vk.com/wall%d_%d");
tszRes.AppendFormat(tszFormat, tszBBCIn.GetBuffer(), TranslateT("Link"), tszBBCOut.GetBuffer(), iOwnerId, iId);
}
else if (vkParentType == vkTopic) {
LONG iOwnerId = json_as_int(json_get(pParent, "owner_id"));
LONG iId = json_as_int(json_get(pParent, "id"));
CMString tszTitle = json_as_string(json_get(pParent, "title"));
CMString tszFormat = _T("%s%s%s\n%s%s%s: https://vk.com/topic%d_%d");
tszRes.AppendFormat(tszFormat, tszBBCIn.GetBuffer(), tszTitle.GetBuffer(), tszBBCOut.GetBuffer(), tszBBCIn.GetBuffer(), TranslateT("Link"), tszBBCOut.GetBuffer(), iOwnerId, iId);
}
else if (vkParentType == vkComment) {
JSONNODE *pNode = json_get(pParent, "photo");
if (pNode)
return GetVkParent(pNode, vkPhoto);
pNode = json_get(pParent, "video");
if (pNode)
return GetVkParent(pNode, vkVideo);
LONG iId = json_as_int(json_get(pParent, "id"));
pNode = json_get(pParent, "post");
if (pNode){
tszRes = GetVkParent(pNode, vkPost);
tszRes.AppendFormat(_T("?reply=%d"), iId);
return tszRes;
}
pNode = json_get(pParent, "topic");
if (pNode){
tszRes = GetVkParent(pNode, vkTopic);
tszRes.AppendFormat(_T("?reply=%d"), iId);
return tszRes;
}
}
return tszRes;
}
CMString CVkProto::GetVkNotificationsItem(JSONNODE *pItem, OBJLIST &vkUsers, time_t &tDate)
{
debugLogA("CVkProto::GetVkNotificationsItem");
CMString tszRes;
if (pItem == NULL)
return tszRes;
CMString tszType = json_as_string(json_get(pItem, "type"));
tDate = json_as_int(json_get(pItem, "date"));
VKObjType vkFeedbackType = vkNull, vkParentType = vkNull;
CMString tszNotificationTranslate = SpanVKNotificationType(tszType, vkFeedbackType, vkParentType);
JSONNODE *pFeedback = json_get(pItem, "feedback");
if (!pFeedback)
return tszRes;
CVkUserInfo *vkUser = NULL;
CMString tszFeedback = GetVkFeedback(pFeedback, vkFeedbackType, vkUsers, vkUser);
JSONNODE *pParent = json_get(pItem, "parent");
if (!pParent)
return tszRes;
CMString tszParent = GetVkParent(pParent, vkParentType);
if (!tszParent.IsEmpty() && !tszFeedback.IsEmpty())
tszRes.AppendFormat(tszFeedback, tszNotificationTranslate.GetBuffer(), tszParent.GetBuffer());
return tszRes;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
void CVkProto::RetrieveUnreadNews(time_t tLastNewsTime)
{
debugLogA("CVkProto::RetrieveUnreadNews");
if (!IsOnline())
return;
Push(new AsyncHttpRequest(this, REQUEST_GET, "/method/newsfeed.get.json", true, &CVkProto::OnReceiveUnreadNews)
<< INT_PARAM("count", 100)
<< INT_PARAM("return_banned", 0)
<< INT_PARAM("max_photos", 100)
<< INT_PARAM("start_time", tLastNewsTime + 1)
<< CHAR_PARAM("filters", "post,photo,photo_tag,wall_photo")
<< VER_API);
}
void CVkProto::OnReceiveUnreadNews(NETLIBHTTPREQUEST *reply, AsyncHttpRequest *pReq)
{
debugLogA("CVkProto::OnReceiveUnreadNews %d", reply->resultCode);
if (reply->resultCode != 200)
return;
JSONROOT pRoot;
JSONNODE *pResponse = CheckJsonResponse(pReq, reply, pRoot);
if (pResponse == NULL)
return;
OBJLIST vkUsers(5, NumericKeySortT);
CreateVkUserInfoList(vkUsers, pResponse);
JSONNODE *pItems = json_get(pResponse, "items");
JSONNODE *pItem;
if (pItems != NULL)
for (int i = 0; (pItem = json_at(pItems, i)) != NULL; i++){
time_t tDate;
CMString tszText = GetVkNewsItem(pItem, vkUsers, tDate);
AddFeedEvent(tszText, tDate);
}
setDword("LastNewsTime", time(NULL));
vkUsers.destroy();
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
void CVkProto::RetrieveUnreadNotifications(time_t tLastNotificationsTime)
{
debugLogA("CVkProto::RetrieveUnreadNotifications");
if (!IsOnline())
return;
Push(new AsyncHttpRequest(this, REQUEST_GET, "/method/notifications.get.json", true, &CVkProto::OnReceiveUnreadNotifications)
<< INT_PARAM("count", 100)
<< INT_PARAM("start_time", tLastNotificationsTime + 1)
<< CHAR_PARAM("filters", "comments,likes,reposts")
<< VER_API);
}
void CVkProto::OnReceiveUnreadNotifications(NETLIBHTTPREQUEST *reply, AsyncHttpRequest *pReq)
{
debugLogA("CVkProto::OnReceiveUnreadNotifications %d", reply->resultCode);
if (reply->resultCode != 200)
return;
JSONROOT pRoot;
JSONNODE *pResponse = CheckJsonResponse(pReq, reply, pRoot);
if (pResponse == NULL)
return;
OBJLIST vkUsers(5, NumericKeySortT);
CreateVkUserInfoList(vkUsers, pResponse);
JSONNODE *pItems = json_get(pResponse, "items");
JSONNODE *pItem;
if (pItems != NULL)
for (int i = 0; (pItem = json_at(pItems, i)) != NULL; i++){
time_t tDate;
CMString tszText = GetVkNotificationsItem(pItem, vkUsers, tDate);
AddFeedEvent(tszText, tDate);
}
setDword("LastNotificationsTime", time(NULL));
vkUsers.destroy();
}
void CVkProto::RetrieveUnreadEvents()
{
debugLogA("CVkProto::RetrieveUnreadEvents");
if (!IsOnline() || (!m_bNotificationsEnabled && !m_bNewsEnabled))
return;
time_t tLastNotificationsTime = getDword("LastNotificationsTime", time(NULL) - 24 * 60 * 60);
if (time(NULL) - tLastNotificationsTime >= m_iNotificationsInterval * 60 && m_bNotificationsEnabled)
RetrieveUnreadNotifications(tLastNotificationsTime);
time_t tLastNewsTime = getDword("LastNewsTime", time(NULL) - 24 * 60 * 60);
if (time(NULL) - tLastNewsTime >= m_iNewsInterval * 60 && m_bNewsEnabled)
RetrieveUnreadNews(tLastNewsTime);
}
INT_PTR CVkProto::SvcLoadVKNews(WPARAM, LPARAM)
{
if (!IsOnline())
return 1;
if (!m_bNewsEnabled && !m_bNotificationsEnabled){
m_bSpecialContactAlwaysEnabled = true;
AddFeedSpecialUser();
}
time_t tLastNewsTime = getDword("LastNewsTime", time(NULL) - 24 * 60 * 60);
RetrieveUnreadNews(tLastNewsTime);
return 0;
}