summaryrefslogtreecommitdiff
path: root/protocols/WhatsApp/src/avatars.cpp
blob: d648bb3801617a9424592e7d7601f6c852b102c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*

WhatsApp plugin for Miranda NG
Copyright © 2019-23 George Hazan

*/

#include "stdafx.h"

void WhatsAppProto::OnIqGetAvatar(const WANode &node)
{
	auto *pUser = FindUser(node.getAttr("from"));
	if (pUser == nullptr)
		return;

	PROTO_AVATAR_INFORMATION ai = {};
	ai.hContact = pUser->hContact;
	ai.format = PA_FORMAT_JPEG;
	wcsncpy_s(ai.filename, GetAvatarFileName(pUser->hContact), _TRUNCATE);

	auto *pNode = node.getChild("picture");

	DWORD dwLastChangeTime = pNode->getAttrInt("id");

	CMStringA szUrl(pNode->getAttr("url"));
	if (szUrl.IsEmpty()) {
		setDword(pUser->hContact, DBKEY_AVATAR_TAG, 0); // avatar doesn't exist, don't check it later

LBL_Error:
		ProtoBroadcastAck(pUser->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, HANDLE(&ai));
		return;
	}

	// if avatar was changed or not present at all, download it
	if (dwLastChangeTime > getDword(pUser->hContact, DBKEY_AVATAR_TAG)) {
		if (!g_plugin.SaveFile(szUrl, ai))
			goto LBL_Error;

		// set timestamp of avatar being saved
		setDword(pUser->hContact, DBKEY_AVATAR_TAG, dwLastChangeTime);
	}
	ProtoBroadcastAck(pUser->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, HANDLE(&ai));
}

INT_PTR WhatsAppProto::GetAvatarInfo(WPARAM wParam, LPARAM lParam)
{
	PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION*)lParam;

	ptrA jid(getStringA(pai->hContact, isChatRoom(pai->hContact) ? "ChatRoomID" : DBKEY_ID));
	if (jid == NULL)
		return GAIR_NOAVATAR;

	CMStringW tszFileName(GetAvatarFileName(pai->hContact));
	wcsncpy_s(pai->filename, tszFileName.c_str(), _TRUNCATE);
	pai->format = PA_FORMAT_JPEG;

	DWORD dwTag = getDword(pai->hContact, DBKEY_AVATAR_TAG, -1);
	if (dwTag == -1 || (wParam & GAIF_FORCE) != 0)
		if (pai->hContact != NULL && isOnline()) {
			ServerFetchAvatar(jid);
			return GAIR_WAITFOR;
		}

	debugLogA("No avatar");
	return GAIR_NOAVATAR;
}

/////////////////////////////////////////////////////////////////////////////////////////

INT_PTR WhatsAppProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam)
{
	switch (wParam) {
	case AF_PROPORTION:
		return PIP_SQUARE;

	case AF_FORMATSUPPORTED: // Jabber supports avatars of virtually all formats
		return PA_FORMAT_JPEG;

	case AF_ENABLED:
		return TRUE;

	case AF_MAXSIZE:
		POINT *size = (POINT*)lParam;
		if (size)
			size->x = size->y = 640;
		return 0;
	}
	return -1;
}

CMStringW WhatsAppProto::GetAvatarFileName(MCONTACT hContact)
{
	CMStringW result = m_tszAvatarFolder + L"\\";

	CMStringA jid;
	if (hContact != NULL) {
		ptrA szId(getStringA(hContact, isChatRoom(hContact) ? "ChatRoomID" : DBKEY_ID));
		if (szId == NULL)
			return L"";

		jid = szId;
	}
	else jid = m_szJid;

	return result + _A2T(jid.c_str()) + L".jpg";
}

INT_PTR WhatsAppProto::GetMyAvatar(WPARAM wParam, LPARAM lParam)
{
	std::wstring tszOwnAvatar(m_tszAvatarFolder + L"\\myavatar.jpg");
	wcsncpy_s((wchar_t*)wParam, lParam, tszOwnAvatar.c_str(), _TRUNCATE);
	return 0;
}

INT_PTR WhatsAppProto::SetMyAvatar(WPARAM, LPARAM)
{
	return 0;
}

/////////////////////////////////////////////////////////////////////////////////////////

void WhatsAppProto::ServerFetchAvatar(const char *jid)
{
	WANodeIq iq(IQ::GET, "w:profile:picture", jid);
	*iq.addChild("picture") << CHAR_PARAM("type", "preview") << CHAR_PARAM("query", "url");
	WSSendNode(iq, &WhatsAppProto::OnIqGetAvatar);
}

/////////////////////////////////////////////////////////////////////////////////////////

bool CMPlugin::SaveFile(const char *pszUrl, PROTO_AVATAR_INFORMATION &ai)
{
	NETLIBHTTPREQUEST req = {};
	req.cbSize = sizeof(req);
	req.flags = NLHRF_NODUMP | NLHRF_PERSISTENT | NLHRF_SSL | NLHRF_HTTP11 | NLHRF_REDIRECT;
	req.requestType = REQUEST_GET;
	req.szUrl = (char*)pszUrl;
	req.nlc = hAvatarConn;

	NETLIBHTTPREQUEST *pReply = Netlib_HttpTransaction(hAvatarUser, &req);
	if (pReply == nullptr) {
		hAvatarConn = nullptr;
		debugLogA("Failed to retrieve avatar from url: %s", pszUrl);
		return false;
	}

	hAvatarConn = pReply->nlc;

	bool bSuccess = false;
	if (pReply->resultCode == 200 && pReply->pData && pReply->dataLength) {
		if (auto *pszHdr = Netlib_GetHeader(pReply, "Content-Type"))
			ai.format = ProtoGetAvatarFormatByMimeType(pszHdr);

		if (ai.format != PA_FORMAT_UNKNOWN) {
			FILE *fout = _wfopen(ai.filename, L"wb");
			if (fout) {
				fwrite(pReply->pData, 1, pReply->dataLength, fout);
				fclose(fout);
				bSuccess = true;
			}
			else debugLogA("Error saving avatar to file %S", ai.filename);
		}
		else debugLogA("unknown avatar mime type");
	}
	else debugLogA("Error %d reading avatar from url: %s", pReply->resultCode, pszUrl);

	Netlib_FreeHttpRequest(pReply);
	return bSuccess;
}