3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * Rewritten from scratch during Google Summer of Code 2012
8 * by Tomek Wasilczyk (http://www.wasilczyk.pl).
10 * Previously implemented by:
11 * - Arkadiusz Miskiewicz <misiek@pld.org.pl> - first implementation (2001);
12 * - Bartosz Oler <bartosz@bzimage.us> - reimplemented during GSoC 2005;
13 * - Krzysztof Klinikowski <grommasher@gmail.com> - some parts (2009-2011).
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
33 #include <glibcompat.h>
38 #include "oauth/oauth-purple.h"
42 static inline ggp_avatar_session_data
*
43 ggp_avatar_get_avdata(PurpleConnection
*gc
);
45 static gboolean
ggp_avatar_timer_cb(gpointer _gc
);
47 #define GGP_AVATAR_USERAGENT "GG Client build 11.0.0.7562"
48 #define GGP_AVATAR_SIZE_MAX 1048576
50 /* Buddy avatars updating */
58 PurpleHttpConnection
*request
;
59 } ggp_avatar_buddy_update_req
;
61 static gboolean
ggp_avatar_buddy_update_next(PurpleConnection
*gc
);
62 static void ggp_avatar_buddy_update_received(PurpleHttpConnection
*http_conn
,
63 PurpleHttpResponse
*response
, gpointer user_data
);
65 #define GGP_AVATAR_BUDDY_URL "http://avatars.gg.pl/%u/s,big"
67 /* Own avatar setting */
72 } ggp_avatar_own_data
;
74 static void ggp_avatar_own_got_token(PurpleConnection
*gc
, const gchar
*token
,
76 static void ggp_avatar_own_sent(PurpleHttpConnection
*http_conn
,
77 PurpleHttpResponse
*response
, gpointer user_data
);
79 #define GGP_AVATAR_RESPONSE_MAX 10240
81 /*******************************************************************************
83 ******************************************************************************/
85 void ggp_avatar_setup(PurpleConnection
*gc
)
87 ggp_avatar_session_data
*avdata
= ggp_avatar_get_avdata(gc
);
89 avdata
->pending_updates
= NULL
;
90 avdata
->current_update
= NULL
;
91 avdata
->own_data
= g_new0(ggp_avatar_own_data
, 1);
93 avdata
->timer
= g_timeout_add_seconds(1, ggp_avatar_timer_cb
, gc
);
96 void ggp_avatar_cleanup(PurpleConnection
*gc
)
98 ggp_avatar_session_data
*avdata
= ggp_avatar_get_avdata(gc
);
100 g_source_remove(avdata
->timer
);
102 if (avdata
->current_update
!= NULL
) {
103 ggp_avatar_buddy_update_req
*current_update
=
104 avdata
->current_update
;
106 purple_http_conn_cancel(current_update
->request
);
107 g_free(current_update
);
109 avdata
->current_update
= NULL
;
111 g_free(avdata
->own_data
);
113 g_list_free_full(avdata
->pending_updates
, &g_free
);
114 avdata
->pending_updates
= NULL
;
117 static inline ggp_avatar_session_data
*
118 ggp_avatar_get_avdata(PurpleConnection
*gc
)
120 GGPInfo
*accdata
= purple_connection_get_protocol_data(gc
);
121 return &accdata
->avatar_data
;
124 static gboolean
ggp_avatar_timer_cb(gpointer _gc
)
126 PurpleConnection
*gc
= _gc
;
127 ggp_avatar_session_data
*avdata
;
129 PURPLE_ASSERT_CONNECTION_IS_VALID(gc
);
131 avdata
= ggp_avatar_get_avdata(gc
);
132 if (avdata
->current_update
!= NULL
) {
133 if (purple_debug_is_verbose()) {
134 purple_debug_misc("gg", "ggp_avatar_timer_cb(%p): "
135 "there is already an update running\n", gc
);
140 while (!ggp_avatar_buddy_update_next(gc
));
145 /*******************************************************************************
146 * Buddy avatars updating.
147 ******************************************************************************/
149 void ggp_avatar_buddy_update(PurpleConnection
*gc
, uin_t uin
, time_t timestamp
)
151 ggp_avatar_session_data
*avdata
= ggp_avatar_get_avdata(gc
);
152 ggp_avatar_buddy_update_req
*pending_update
=
153 g_new(ggp_avatar_buddy_update_req
, 1); /* TODO: leak? */
155 if (purple_debug_is_verbose()) {
156 purple_debug_misc("gg", "ggp_avatar_buddy_update(%p, %u, %lu)\n", gc
,
160 pending_update
->uin
= uin
;
161 pending_update
->timestamp
= timestamp
;
163 avdata
->pending_updates
= g_list_append(avdata
->pending_updates
,
167 void ggp_avatar_buddy_remove(PurpleConnection
*gc
, uin_t uin
)
169 if (purple_debug_is_verbose()) {
170 purple_debug_misc("gg", "ggp_avatar_buddy_remove(%p, %u)\n", gc
, uin
);
173 purple_buddy_icons_set_for_user(purple_connection_get_account(gc
),
174 ggp_uin_to_str(uin
), NULL
, 0, NULL
);
177 /* return TRUE if avatar update was performed or there is no new requests,
178 FALSE if we can request another one immediately */
179 static gboolean
ggp_avatar_buddy_update_next(PurpleConnection
*gc
)
181 PurpleHttpRequest
*req
;
182 ggp_avatar_session_data
*avdata
= ggp_avatar_get_avdata(gc
);
183 GList
*pending_update_it
;
184 ggp_avatar_buddy_update_req
*pending_update
;
186 PurpleAccount
*account
= purple_connection_get_account(gc
);
187 time_t old_timestamp
;
188 const char *old_timestamp_str
;
190 pending_update_it
= g_list_first(avdata
->pending_updates
);
191 if (pending_update_it
== NULL
)
194 pending_update
= pending_update_it
->data
;
195 avdata
->pending_updates
= g_list_remove(avdata
->pending_updates
,
197 buddy
= purple_blist_find_buddy(account
, ggp_uin_to_str(pending_update
->uin
));
200 if (ggp_str_to_uin(purple_account_get_username(account
)) ==
203 purple_debug_misc("gg",
204 "ggp_avatar_buddy_update_next(%p): own "
205 "avatar update requested, but we don't have "
206 "ourselves on buddy list\n", gc
);
208 purple_debug_warning("gg",
209 "ggp_avatar_buddy_update_next(%p): "
210 "%u update requested, but he's not on buddy "
211 "list\n", gc
, pending_update
->uin
);
216 old_timestamp_str
= purple_buddy_icons_get_checksum_for_user(buddy
);
217 old_timestamp
= old_timestamp_str
? g_ascii_strtoull(
218 old_timestamp_str
, NULL
, 10) : 0;
219 if (old_timestamp
== pending_update
->timestamp
) {
220 if (purple_debug_is_verbose()) {
221 purple_debug_misc("gg",
222 "ggp_avatar_buddy_update_next(%p): "
223 "%u have up to date avatar with ts=%lu\n", gc
,
224 pending_update
->uin
, pending_update
->timestamp
);
228 if (old_timestamp
> pending_update
->timestamp
) {
229 purple_debug_warning("gg",
230 "ggp_avatar_buddy_update_next(%p): "
231 "saved timestamp for %u is newer than received "
232 "(%lu > %lu)\n", gc
, pending_update
->uin
, old_timestamp
,
233 pending_update
->timestamp
);
236 purple_debug_info("gg",
237 "ggp_avatar_buddy_update_next(%p): "
238 "updating %u with ts=%lu...\n", gc
, pending_update
->uin
,
239 pending_update
->timestamp
);
241 pending_update
->gc
= gc
;
242 avdata
->current_update
= pending_update
;
244 req
= purple_http_request_new(NULL
);
245 purple_http_request_set_url_printf(req
, GGP_AVATAR_BUDDY_URL
,
246 pending_update
->uin
);
247 purple_http_request_header_set(req
, "User-Agent", GGP_AVATAR_USERAGENT
);
248 purple_http_request_set_max_len(req
, GGP_AVATAR_SIZE_MAX
);
249 pending_update
->request
= purple_http_request(gc
, req
,
250 ggp_avatar_buddy_update_received
, pending_update
);
251 purple_http_request_unref(req
);
256 static void ggp_avatar_buddy_update_received(PurpleHttpConnection
*http_conn
,
257 PurpleHttpResponse
*response
, gpointer _pending_update
)
259 ggp_avatar_buddy_update_req
*pending_update
= _pending_update
;
261 PurpleAccount
*account
;
262 PurpleConnection
*gc
= pending_update
->gc
;
263 ggp_avatar_session_data
*avdata
;
264 gchar timestamp_str
[20];
265 const gchar
*got_data
;
268 PURPLE_ASSERT_CONNECTION_IS_VALID(gc
);
270 avdata
= ggp_avatar_get_avdata(gc
);
271 g_assert(pending_update
== avdata
->current_update
);
272 avdata
->current_update
= NULL
;
274 if (!purple_http_response_is_successful(response
)) {
275 purple_debug_error("gg", "ggp_avatar_buddy_update_received: bad"
276 " response while getting avatar for %u: %s\n",
278 purple_http_response_get_error(response
));
279 g_free(pending_update
);
283 account
= purple_connection_get_account(gc
);
284 buddy
= purple_blist_find_buddy(account
, ggp_uin_to_str(pending_update
->uin
));
287 purple_debug_warning("gg", "ggp_avatar_buddy_update_received: "
288 "buddy %u disappeared\n", pending_update
->uin
);
289 g_free(pending_update
);
293 g_snprintf(timestamp_str
, sizeof(timestamp_str
), "%lu",
294 pending_update
->timestamp
);
295 got_data
= purple_http_response_get_data(response
, &got_len
);
296 purple_buddy_icons_set_for_user(account
, purple_buddy_get_name(buddy
),
297 g_memdup(got_data
, got_len
), got_len
, timestamp_str
);
299 purple_debug_info("gg", "ggp_avatar_buddy_update_received: "
300 "got avatar for buddy %u [ts=%lu]\n", pending_update
->uin
,
301 pending_update
->timestamp
);
302 g_free(pending_update
);
305 /*******************************************************************************
306 * Own avatar setting.
307 ******************************************************************************/
310 * TODO: use new, GG11 method, when IMToken will be provided by libgadu.
312 * POST https://avatars.mpa.gg.pl/avatars/user,<uin>/0
313 * Authorization: IMToken 0123456789abcdef0123456789abcdef01234567
314 * photo=<avatar content>
317 void ggp_avatar_own_set(PurpleConnection
*gc
, PurpleImage
*img
)
319 ggp_avatar_own_data
*own_data
;
321 PURPLE_ASSERT_CONNECTION_IS_VALID(gc
);
323 purple_debug_info("gg", "ggp_avatar_own_set(%p, %p)", gc
, img
);
325 own_data
= ggp_avatar_get_avdata(gc
)->own_data
;
328 purple_debug_warning("gg", "ggp_avatar_own_set: avatar removing"
329 " is probably not possible within old protocol");
335 ggp_oauth_request(gc
, ggp_avatar_own_got_token
, img
, NULL
, NULL
);
339 ggp_avatar_own_got_token(PurpleConnection
*gc
, const gchar
*token
,
342 PurpleHttpRequest
*req
;
343 PurpleImage
*img
= _img
;
344 ggp_avatar_own_data
*own_data
= ggp_avatar_get_avdata(gc
)->own_data
;
345 gchar
*img_data
, *img_data_e
, *request_data
;
346 PurpleAccount
*account
= purple_connection_get_account(gc
);
347 uin_t uin
= ggp_str_to_uin(purple_account_get_username(account
));
349 if (img
!= own_data
->img
) {
350 purple_debug_warning("gg", "ggp_avatar_own_got_token: "
351 "avatar was changed in meantime\n");
354 own_data
->img
= NULL
;
356 img_data
= g_base64_encode(purple_image_get_data(img
),
357 purple_image_get_data_size(img
));
358 img_data_e
= g_uri_escape_string(img_data
, NULL
, FALSE
);
360 request_data
= g_strdup_printf("uin=%d&photo=%s", uin
, img_data_e
);
363 purple_debug_misc("gg", "ggp_avatar_own_got_token: "
364 "uploading new avatar...\n");
366 req
= purple_http_request_new("http://avatars.nowe.gg/upload");
367 purple_http_request_set_max_len(req
, GGP_AVATAR_RESPONSE_MAX
);
368 purple_http_request_set_method(req
, "POST");
369 purple_http_request_header_set(req
, "Authorization", token
);
370 purple_http_request_header_set(req
, "From", "avatars to avatars");
371 purple_http_request_header_set(req
, "Content-Type",
372 "application/x-www-form-urlencoded");
373 purple_http_request_set_contents(req
, request_data
, -1);
374 purple_http_request(gc
, req
, ggp_avatar_own_sent
, NULL
);
375 purple_http_request_unref(req
);
377 g_free(request_data
);
380 static void ggp_avatar_own_sent(PurpleHttpConnection
*http_conn
,
381 PurpleHttpResponse
*response
, gpointer user_data
)
383 PurpleConnection
*gc
=
384 purple_http_conn_get_purple_connection(http_conn
);
386 PURPLE_ASSERT_CONNECTION_IS_VALID(gc
);
388 if (!purple_http_response_is_successful(response
)) {
389 purple_debug_error("gg", "ggp_avatar_own_sent: "
390 "avatar not sent. %s\n",
391 purple_http_response_get_error(response
));
394 purple_debug_info("gg", "ggp_avatar_own_sent: %s\n",
395 purple_http_response_get_data(response
, NULL
));