Mercurial > pidgin
comparison libpurple/protocols/myspace/myspace.c @ 24986:f0c2e27c7ae7
propagate from branch 'im.pidgin.pidgin' (head b478b184a46116ac87ac85b8cd352ea564224019)
to branch 'im.pidgin.pidgin.vv' (head 6ace5b357bd34d1a93fad3fcf59b31f846c64e0b)
author | Mike Ruprecht <maiku@soc.pidgin.im> |
---|---|
date | Fri, 02 Jan 2009 23:14:27 +0000 |
parents | 68f4edb42f39 c6cadb7bdcf7 |
children | 0d6bd0eca4cb |
comparison
equal
deleted
inserted
replaced
24985:ee2a2a9dda01 | 24986:f0c2e27c7ae7 |
---|---|
1 /* MySpaceIM Protocol Plugin | 1 /** |
2 * MySpaceIM Protocol Plugin | |
2 * | 3 * |
3 * \author Jeff Connelly | 4 * \author Jeff Connelly |
4 * | 5 * |
5 * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im> | 6 * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im> |
6 * | 7 * |
33 | 34 |
34 #define PURPLE_PLUGIN | 35 #define PURPLE_PLUGIN |
35 | 36 |
36 #include "myspace.h" | 37 #include "myspace.h" |
37 | 38 |
38 /* Internal functions */ | 39 #include "privacy.h" |
39 | 40 |
40 #ifdef MSIM_DEBUG_MSG | 41 static void msim_set_status(PurpleAccount *account, PurpleStatus *status); |
41 static void print_hash_item(gpointer key, gpointer value, gpointer user_data); | 42 static void msim_set_idle(PurpleConnection *gc, int time); |
42 #endif | 43 |
43 | 44 /** |
44 static int msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes); | 45 * Perform actual postprocessing on a message, adding userid as specified. |
45 static gboolean msim_login_challenge(MsimSession *session, MsimMessage *msg); | 46 * |
46 static gchar *msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], const gchar *email, const gchar *password, guint *response_len); | 47 * @param msg The message to postprocess. |
47 | 48 * @param uid_before Name of field where to insert new field before, or NULL for end. |
48 static gboolean msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg); | 49 * @param uid_field_name Name of field to add uid to. |
49 static gboolean msim_incoming_bm(MsimSession *session, MsimMessage *msg); | 50 * @param uid The userid to insert. |
50 static gboolean msim_incoming_status(MsimSession *session, MsimMessage *msg); | 51 * |
51 static gboolean msim_incoming_im(MsimSession *session, MsimMessage *msg); | 52 * If the field named by uid_field_name already exists, then its string contents will |
52 /* static gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg); - in zap.c */ | 53 * be used for the field, except "<uid>" will be replaced by the userid. |
53 static gboolean msim_incoming_action(MsimSession *session, MsimMessage *msg); | 54 * |
54 static gboolean msim_incoming_media(MsimSession *session, MsimMessage *msg); | 55 * If the field named by uid_field_name does not exist, it will be added before the |
55 static gboolean msim_incoming_unofficial_client(MsimSession *session, | 56 * field named by uid_before, as an integer, with the userid. |
56 MsimMessage *msg); | 57 * |
57 | 58 * Does not handle sending, or scheduling userid lookup. For that, see msim_postprocess_outgoing(). |
58 #ifdef MSIM_SEND_CLIENT_VERSION | 59 */ |
59 static gboolean msim_send_unofficial_client(MsimSession *session, gchar *username); | 60 static MsimMessage * |
60 #endif | 61 msim_do_postprocessing(MsimMessage *msg, const gchar *uid_before, |
61 | 62 const gchar *uid_field_name, guint uid) |
62 static void msim_get_info_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); | 63 { |
63 | 64 MsimMessageElement *elem; |
64 static void msim_set_status_code(MsimSession *session, guint code, gchar *statstring); | 65 |
65 | 66 /* First, check - if the field already exists, replace <uid> within it */ |
66 static gboolean msim_process_server_info(MsimSession *session, MsimMessage *msg); | 67 if ((elem = msim_msg_get(msg, uid_field_name)) != NULL) { |
67 static gboolean msim_web_challenge(MsimSession *session, MsimMessage *msg); | 68 gchar *fmt_string; |
68 static gboolean msim_process_reply(MsimSession *session, MsimMessage *msg); | 69 gchar *uid_str, *new_str; |
69 | 70 |
70 static gboolean msim_preprocess_incoming(MsimSession *session, MsimMessage *msg); | 71 /* Get the packed element, flattening it. This allows <uid> to be |
71 | 72 * replaced within nested data structures, since the replacement is done |
72 #ifdef MSIM_USE_KEEPALIVE | 73 * on the linear, packed data, not on a complicated data structure. |
73 static gboolean msim_check_alive(gpointer data); | 74 * |
74 #endif | 75 * For example, if the field was originally a dictionary or a list, you |
75 | 76 * would have to iterate over all the items in it to see what needs to |
76 static gboolean msim_is_username_set(MsimSession *session, MsimMessage *msg); | 77 * be replaced. But by packing it first, the <uid> marker is easily replaced |
77 | 78 * just by a string replacement. |
78 static gboolean msim_process(MsimSession *session, MsimMessage *msg); | 79 */ |
79 | 80 fmt_string = msim_msg_pack_element_data(elem); |
80 static MsimMessage *msim_do_postprocessing(MsimMessage *msg, const gchar *uid_field_name, const gchar *uid_before, guint uid); | 81 |
81 static void msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); | 82 uid_str = g_strdup_printf("%d", uid); |
82 static gboolean msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, const gchar *username, const gchar *uid_field_name, const gchar *uid_before); | 83 new_str = purple_strreplace(fmt_string, "<uid>", uid_str); |
83 | 84 g_free(uid_str); |
84 static gboolean msim_error(MsimSession *session, MsimMessage *msg); | 85 g_free(fmt_string); |
85 | 86 |
86 static void msim_check_inbox_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); | 87 /* Free the old element data */ |
87 static gboolean msim_check_inbox(gpointer data); | 88 msim_msg_free_element_data(elem->data); |
88 | 89 |
89 static void msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond); | 90 /* Replace it with our new data */ |
90 | 91 elem->data = new_str; |
91 | 92 elem->type = MSIM_TYPE_RAW; |
92 static void msim_connect_cb(gpointer data, gint source, const gchar *error_message); | 93 |
93 | 94 } else { |
94 static void msim_import_friends(PurplePluginAction *action); | 95 /* Otherwise, insert new field into outgoing message. */ |
95 static void msim_import_friends_cb(MsimSession *session, MsimMessage *reply, gpointer user_data); | 96 msg = msim_msg_insert_before(msg, uid_before, uid_field_name, MSIM_TYPE_INTEGER, GUINT_TO_POINTER(uid)); |
96 static gboolean msim_get_contact_list(MsimSession *session, int what_to_do_after); | 97 } |
97 | 98 |
98 static gboolean msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params); | 99 return msg; |
99 static void msim_uri_handler_addContact_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); | 100 } |
100 static void msim_uri_handler_sendIM_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); | 101 |
101 | 102 /** |
102 /** | 103 * Callback for msim_postprocess_outgoing() to add a userid to a message, and send it (once receiving userid). |
103 * Load the plugin. | 104 * |
104 */ | 105 * @param session |
105 gboolean | 106 * @param userinfo The user information reply message, containing the user ID |
106 msim_load(PurplePlugin *plugin) | 107 * @param data The message to postprocess and send. |
107 { | 108 * |
108 /* If compiled to use RC4 from libpurple, check if it is really there. */ | 109 * The data message should contain these fields: |
109 if (!purple_ciphers_find_cipher("rc4")) { | 110 * |
110 purple_debug_error("msim", "rc4 not in libpurple, but it is required - not loading MySpaceIM plugin!\n"); | 111 * _uid_field_name: string, name of field to add with userid from userinfo message |
111 purple_notify_error(plugin, _("Missing Cipher"), | 112 * _uid_before: string, name of field before field to insert, or NULL for end |
112 _("The RC4 cipher could not be found"), | 113 */ |
113 _("Upgrade " | 114 static void |
114 "to a libpurple with RC4 support (>= 2.0.1). MySpaceIM " | 115 msim_postprocess_outgoing_cb(MsimSession *session, const MsimMessage *userinfo, |
115 "plugin will not be loaded.")); | 116 gpointer data) |
116 return FALSE; | 117 { |
117 } | 118 gchar *uid_field_name, *uid_before, *username; |
118 return TRUE; | 119 guint uid; |
120 MsimMessage *msg, *body; | |
121 | |
122 msg = (MsimMessage *)data; | |
123 | |
124 /* Obtain userid from userinfo message. */ | |
125 body = msim_msg_get_dictionary(userinfo, "body"); | |
126 g_return_if_fail(body != NULL); | |
127 | |
128 uid = msim_msg_get_integer(body, "UserID"); | |
129 msim_msg_free(body); | |
130 | |
131 username = msim_msg_get_string(msg, "_username"); | |
132 | |
133 if (!uid) { | |
134 gchar *msg; | |
135 | |
136 msg = g_strdup_printf(_("No such user: %s"), username); | |
137 if (!purple_conv_present_error(username, session->account, msg)) { | |
138 purple_notify_error(NULL, NULL, _("User lookup"), msg); | |
139 } | |
140 | |
141 g_free(msg); | |
142 g_free(username); | |
143 /* TODO: free | |
144 * msim_msg_free(msg); | |
145 */ | |
146 return; | |
147 } | |
148 | |
149 uid_field_name = msim_msg_get_string(msg, "_uid_field_name"); | |
150 uid_before = msim_msg_get_string(msg, "_uid_before"); | |
151 | |
152 msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid); | |
153 | |
154 /* Send */ | |
155 if (!msim_msg_send(session, msg)) { | |
156 msim_msg_dump("msim_postprocess_outgoing_cb: sending failed for message: %s\n", msg); | |
157 } | |
158 | |
159 | |
160 /* Free field names AFTER sending message, because MsimMessage does NOT copy | |
161 * field names - instead, treats them as static strings (which they usually are). | |
162 */ | |
163 g_free(uid_field_name); | |
164 g_free(uid_before); | |
165 g_free(username); | |
166 /* TODO: free | |
167 * msim_msg_free(msg); | |
168 */ | |
169 } | |
170 | |
171 /** | |
172 * Postprocess and send a message. | |
173 * | |
174 * @param session | |
175 * @param msg Message to postprocess. Will NOT be freed. | |
176 * @param username Username to resolve. Assumed to be a static string (will not be freed or copied). | |
177 * @param uid_field_name Name of new field to add, containing uid of username. Static string. | |
178 * @param uid_before Name of existing field to insert username field before. Static string. | |
179 * | |
180 * @return TRUE if successful. | |
181 */ | |
182 static gboolean | |
183 msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, | |
184 const gchar *username, const gchar *uid_field_name, | |
185 const gchar *uid_before) | |
186 { | |
187 PurpleBuddy *buddy; | |
188 guint uid; | |
189 gboolean rc; | |
190 | |
191 g_return_val_if_fail(msg != NULL, FALSE); | |
192 | |
193 /* Store information for msim_postprocess_outgoing_cb(). */ | |
194 msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username)); | |
195 msg = msim_msg_append(msg, "_uid_field_name", MSIM_TYPE_STRING, g_strdup(uid_field_name)); | |
196 msg = msim_msg_append(msg, "_uid_before", MSIM_TYPE_STRING, g_strdup(uid_before)); | |
197 | |
198 /* First, try the most obvious. If numeric userid is given, use that directly. */ | |
199 if (msim_is_userid(username)) { | |
200 uid = atol(username); | |
201 } else { | |
202 /* Next, see if on buddy list and know uid. */ | |
203 buddy = purple_find_buddy(session->account, username); | |
204 if (buddy) { | |
205 uid = purple_blist_node_get_int(&buddy->node, "UserID"); | |
206 } else { | |
207 uid = 0; | |
208 } | |
209 | |
210 if (!buddy || !uid) { | |
211 /* Don't have uid offhand - need to ask for it, and wait until hear back before sending. */ | |
212 purple_debug_info("msim", ">>> msim_postprocess_outgoing: couldn't find username %s in blist\n", | |
213 username ? username : "(NULL)"); | |
214 msim_lookup_user(session, username, msim_postprocess_outgoing_cb, msim_msg_clone(msg)); | |
215 return TRUE; /* not sure of status yet - haven't sent! */ | |
216 } | |
217 } | |
218 | |
219 /* Already have uid, postprocess and send msg immediately. */ | |
220 purple_debug_info("msim", "msim_postprocess_outgoing: found username %s has uid %d\n", | |
221 username ? username : "(NULL)", uid); | |
222 | |
223 msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid); | |
224 | |
225 rc = msim_msg_send(session, msg); | |
226 | |
227 /* TODO: free | |
228 * msim_msg_free(msg); | |
229 */ | |
230 | |
231 return rc; | |
232 } | |
233 | |
234 /** | |
235 * Send a buddy message of a given type. | |
236 * | |
237 * @param session | |
238 * @param who Username to send message to. | |
239 * @param text Message text to send. Not freed; will be copied. | |
240 * @param type A MSIM_BM_* constant. | |
241 * | |
242 * @return TRUE if success, FALSE if fail. | |
243 * | |
244 * Buddy messages ('bm') include instant messages, action messages, status messages, etc. | |
245 */ | |
246 gboolean | |
247 msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, | |
248 int type) | |
249 { | |
250 gboolean rc; | |
251 MsimMessage *msg; | |
252 const gchar *from_username; | |
253 | |
254 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
255 g_return_val_if_fail(who != NULL, FALSE); | |
256 g_return_val_if_fail(text != NULL, FALSE); | |
257 | |
258 from_username = session->account->username; | |
259 | |
260 g_return_val_if_fail(from_username != NULL, FALSE); | |
261 | |
262 purple_debug_info("msim", "sending %d message from %s to %s: %s\n", | |
263 type, from_username, who, text); | |
264 | |
265 msg = msim_msg_new( | |
266 "bm", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(type), | |
267 "sesskey", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(session->sesskey), | |
268 /* 't' will be inserted here */ | |
269 "cv", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(MSIM_CLIENT_VERSION), | |
270 "msg", MSIM_TYPE_STRING, g_strdup(text), | |
271 NULL); | |
272 | |
273 rc = msim_postprocess_outgoing(session, msg, who, "t", "cv"); | |
274 | |
275 msim_msg_free(msg); | |
276 | |
277 return rc; | |
278 } | |
279 | |
280 /** | |
281 * Lookup a username by userid, from buddy list. | |
282 * | |
283 * @param wanted_uid | |
284 * | |
285 * @return Username of wanted_uid, if on blist, or NULL. | |
286 * This is a static string, so don't free it. Copy it if needed. | |
287 * | |
288 */ | |
289 static const gchar * | |
290 msim_uid2username_from_blist(PurpleAccount *account, guint wanted_uid) | |
291 { | |
292 GSList *buddies, *cur; | |
293 const gchar *ret; | |
294 | |
295 buddies = purple_find_buddies(account, NULL); | |
296 | |
297 if (!buddies) | |
298 { | |
299 purple_debug_info("msim", "msim_uid2username_from_blist: no buddies?\n"); | |
300 return NULL; | |
301 } | |
302 | |
303 ret = NULL; | |
304 | |
305 for (cur = buddies; cur != NULL; cur = g_slist_next(cur)) | |
306 { | |
307 PurpleBuddy *buddy; | |
308 guint uid; | |
309 const gchar *name; | |
310 | |
311 /* See finch/gnthistory.c */ | |
312 buddy = cur->data; | |
313 | |
314 uid = purple_blist_node_get_int(&buddy->node, "UserID"); | |
315 name = purple_buddy_get_name(buddy); | |
316 | |
317 if (uid == wanted_uid) | |
318 { | |
319 ret = name; | |
320 break; | |
321 } | |
322 } | |
323 | |
324 g_slist_free(buddies); | |
325 return ret; | |
326 } | |
327 | |
328 /** | |
329 * Setup a callback, to be called when a reply is received with the returned rid. | |
330 * | |
331 * @param cb The callback, an MSIM_USER_LOOKUP_CB. | |
332 * @param data Arbitrary user data to be passed to callback (probably an MsimMessage *). | |
333 * | |
334 * @return The request/reply ID, used to link replies with requests, or -1. | |
335 * Put the rid in your request, 'rid' field. | |
336 * | |
337 * TODO: Make more generic and more specific: | |
338 * 1) MSIM_USER_LOOKUP_CB - make it for PERSIST_REPLY, not just user lookup | |
339 * 2) data - make it an MsimMessage? | |
340 */ | |
341 guint | |
342 msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb, | |
343 gpointer data) | |
344 { | |
345 guint rid; | |
346 | |
347 g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); | |
348 | |
349 rid = session->next_rid++; | |
350 | |
351 g_hash_table_insert(session->user_lookup_cb, GUINT_TO_POINTER(rid), cb); | |
352 g_hash_table_insert(session->user_lookup_cb_data, GUINT_TO_POINTER(rid), data); | |
353 | |
354 return rid; | |
355 } | |
356 | |
357 /** | |
358 * Return the icon name for a buddy and account. | |
359 * | |
360 * @param acct The account to find the icon for, or NULL for protocol icon. | |
361 * @param buddy The buddy to find the icon for, or NULL for the account icon. | |
362 * | |
363 * @return The base icon name string. | |
364 */ | |
365 static const gchar * | |
366 msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy) | |
367 { | |
368 /* Use a MySpace icon submitted by hbons at | |
369 * http://developer.pidgin.im/wiki/MySpaceIM. */ | |
370 return "myspace"; | |
371 } | |
372 | |
373 /** | |
374 * Obtain the status text for a buddy. | |
375 * | |
376 * @param buddy The buddy to obtain status text for. | |
377 * | |
378 * @return Status text, or NULL if error. Caller g_free()'s. | |
379 */ | |
380 static char * | |
381 msim_status_text(PurpleBuddy *buddy) | |
382 { | |
383 MsimSession *session; | |
384 MsimUser *user; | |
385 const gchar *display_name, *headline; | |
386 | |
387 g_return_val_if_fail(buddy != NULL, NULL); | |
388 | |
389 user = msim_get_user_from_buddy(buddy); | |
390 | |
391 session = (MsimSession *)buddy->account->gc->proto_data; | |
392 g_return_val_if_fail(MSIM_SESSION_VALID(session), NULL); | |
393 | |
394 display_name = headline = NULL; | |
395 | |
396 /* Retrieve display name and/or headline, depending on user preference. */ | |
397 if (purple_account_get_bool(session->account, "show_headline", TRUE)) { | |
398 headline = user->headline; | |
399 } | |
400 | |
401 if (purple_account_get_bool(session->account, "show_display_name", FALSE)) { | |
402 display_name = user->display_name; | |
403 } | |
404 | |
405 /* Return appropriate combination of display name and/or headline, or neither. */ | |
406 | |
407 if (display_name && headline) { | |
408 return g_strconcat(display_name, " ", headline, NULL); | |
409 } else if (display_name) { | |
410 return g_strdup(display_name); | |
411 } else if (headline) { | |
412 return g_strdup(headline); | |
413 } | |
414 | |
415 return NULL; | |
416 } | |
417 | |
418 /** | |
419 * Obtain the tooltip text for a buddy. | |
420 * | |
421 * @param buddy Buddy to obtain tooltip text on. | |
422 * @param user_info Variable modified to have the tooltip text. | |
423 * @param full TRUE if should obtain full tooltip text. | |
424 */ | |
425 static void | |
426 msim_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, | |
427 gboolean full) | |
428 { | |
429 MsimUser *user; | |
430 | |
431 g_return_if_fail(buddy != NULL); | |
432 g_return_if_fail(user_info != NULL); | |
433 | |
434 user = msim_get_user_from_buddy(buddy); | |
435 | |
436 if (PURPLE_BUDDY_IS_ONLINE(buddy)) { | |
437 MsimSession *session; | |
438 | |
439 session = (MsimSession *)buddy->account->gc->proto_data; | |
440 | |
441 g_return_if_fail(MSIM_SESSION_VALID(session)); | |
442 | |
443 /* TODO: if (full), do something different? */ | |
444 | |
445 /* TODO: request information? have to figure out how to do | |
446 * the asynchronous lookup like oscar does (tooltip shows | |
447 * 'retrieving...' if not yet available, then changes when it is). | |
448 * | |
449 * Right now, only show what we have on hand. | |
450 */ | |
451 | |
452 /* Show abbreviated user info. */ | |
453 msim_append_user_info(session, user_info, user, FALSE); | |
454 } | |
119 } | 455 } |
120 | 456 |
121 /** | 457 /** |
122 * Get possible user status types. Based on mockprpl. | 458 * Get possible user status types. Based on mockprpl. |
123 * | 459 * |
124 * @return GList of status types. | 460 * @return GList of status types. |
125 */ | 461 */ |
126 GList * | 462 static GList * |
127 msim_status_types(PurpleAccount *acct) | 463 msim_status_types(PurpleAccount *acct) |
128 { | 464 { |
129 GList *types; | 465 GList *types; |
130 PurpleStatusType *status; | 466 PurpleStatusType *status; |
131 | 467 |
175 | 511 |
176 return types; | 512 return types; |
177 } | 513 } |
178 | 514 |
179 /** | 515 /** |
180 * Return the icon name for a buddy and account. | 516 * Compute the base64'd login challenge response based on username, password, nonce, and IPs. |
181 * | 517 * |
182 * @param acct The account to find the icon for, or NULL for protocol icon. | 518 * @param nonce The base64 encoded nonce ('nc') field from the server. |
183 * @param buddy The buddy to find the icon for, or NULL for the account icon. | 519 * @param email User's email address (used as login name). |
184 * | 520 * @param password User's cleartext password. |
185 * @return The base icon name string. | 521 * @param response_len Will be written with response length. |
186 */ | 522 * |
187 const gchar * | 523 * @return Binary login challenge response, ready to send to the server. |
188 msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy) | 524 * Must be g_free()'d when finished. NULL if error. |
189 { | 525 */ |
190 /* Use a MySpace icon submitted by hbons at | 526 static gchar * |
191 * http://developer.pidgin.im/wiki/MySpaceIM. */ | 527 msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], |
192 return "myspace"; | 528 const gchar *email, const gchar *password, guint *response_len) |
193 } | 529 { |
194 | 530 PurpleCipherContext *key_context; |
195 #ifdef MSIM_DEBUG_MSG | 531 PurpleCipher *sha1; |
196 static void | 532 PurpleCipherContext *rc4; |
197 print_hash_item(gpointer key, gpointer value, gpointer user_data) | 533 |
198 { | 534 guchar hash_pw[HASH_SIZE]; |
199 purple_debug_info("msim", "%s=%s\n", | 535 guchar key[HASH_SIZE]; |
200 key ? (gchar *)key : "(NULL)", | 536 gchar *password_utf16le, *password_utf8_lc; |
201 value ? (gchar *)value : "(NULL)"); | 537 GString *data; |
202 } | 538 guchar *data_out; |
539 size_t data_out_len; | |
540 gsize conv_bytes_read, conv_bytes_written; | |
541 GError *conv_error; | |
542 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE | |
543 int i; | |
203 #endif | 544 #endif |
204 | 545 |
205 /** | 546 g_return_val_if_fail(nonce != NULL, NULL); |
206 * Send raw data (given as a NUL-terminated string) to the server. | 547 g_return_val_if_fail(email != NULL, NULL); |
207 * | 548 g_return_val_if_fail(password != NULL, NULL); |
208 * @param session | 549 g_return_val_if_fail(response_len != NULL, NULL); |
209 * @param msg The raw data to send, in a NUL-terminated string. | 550 |
210 * | 551 /* Convert password to lowercase (required for passwords containing |
211 * @return TRUE if succeeded, FALSE if not. | 552 * uppercase characters). MySpace passwords are lowercase, |
212 * | 553 * see ticket #2066. */ |
213 */ | 554 password_utf8_lc = g_utf8_strdown(password, -1); |
214 gboolean | 555 |
215 msim_send_raw(MsimSession *session, const gchar *msg) | 556 /* Convert ASCII password to UTF16 little endian */ |
216 { | 557 purple_debug_info("msim", "converting password to UTF-16LE\n"); |
217 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | 558 conv_error = NULL; |
218 g_return_val_if_fail(msg != NULL, FALSE); | 559 password_utf16le = g_convert(password_utf8_lc, -1, "UTF-16LE", "UTF-8", |
219 | 560 &conv_bytes_read, &conv_bytes_written, &conv_error); |
220 purple_debug_info("msim", "msim_send_raw: writing <%s>\n", msg); | 561 g_free(password_utf8_lc); |
221 | 562 |
222 return msim_send_really_raw(session->gc, msg, strlen(msg)) == | 563 g_return_val_if_fail(conv_bytes_read == strlen(password), NULL); |
223 strlen(msg); | 564 |
224 } | 565 if (conv_error != NULL) { |
225 | 566 purple_debug_error("msim", |
226 /** Send raw data to the server, possibly with embedded NULs. | 567 "g_convert password UTF8->UTF16LE failed: %s", |
227 * | 568 conv_error->message); |
228 * Used in prpl_info struct, so that plugins can have the most possible | 569 g_error_free(conv_error); |
229 * control of what is sent over the connection. Inside this prpl, | 570 return NULL; |
230 * msim_send_raw() is used, since it sends NUL-terminated strings (easier). | 571 } |
231 * | 572 |
232 * @param gc PurpleConnection | 573 /* Compute password hash */ |
233 * @param buf Buffer to send | 574 purple_cipher_digest_region("sha1", (guchar *)password_utf16le, |
234 * @param total_bytes Size of buffer to send | 575 conv_bytes_written, sizeof(hash_pw), hash_pw, NULL); |
235 * | 576 g_free(password_utf16le); |
236 * @return Bytes successfully sent, or -1 on error. | 577 |
237 */ | 578 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE |
238 static int | 579 purple_debug_info("msim", "pwhash = "); |
239 msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes) | 580 for (i = 0; i < sizeof(hash_pw); i++) |
240 { | 581 purple_debug_info("msim", "%.2x ", hash_pw[i]); |
241 int total_bytes_sent; | 582 purple_debug_info("msim", "\n"); |
242 MsimSession *session; | 583 #endif |
243 | 584 |
244 g_return_val_if_fail(gc != NULL, -1); | 585 /* key = sha1(sha1(pw) + nonce2) */ |
245 g_return_val_if_fail(buf != NULL, -1); | 586 sha1 = purple_ciphers_find_cipher("sha1"); |
246 g_return_val_if_fail(total_bytes >= 0, -1); | 587 key_context = purple_cipher_context_new(sha1, NULL); |
247 | 588 purple_cipher_context_append(key_context, hash_pw, HASH_SIZE); |
248 session = (MsimSession *)gc->proto_data; | 589 purple_cipher_context_append(key_context, (guchar *)(nonce + NONCE_SIZE), NONCE_SIZE); |
249 | 590 purple_cipher_context_digest(key_context, sizeof(key), key, NULL); |
250 g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); | 591 purple_cipher_context_destroy(key_context); |
251 | 592 |
252 /* Loop until all data is sent, or a failure occurs. */ | 593 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE |
253 total_bytes_sent = 0; | 594 purple_debug_info("msim", "key = "); |
254 do { | 595 for (i = 0; i < sizeof(key); i++) { |
255 int bytes_sent; | 596 purple_debug_info("msim", "%.2x ", key[i]); |
256 | 597 } |
257 bytes_sent = send(session->fd, buf + total_bytes_sent, | 598 purple_debug_info("msim", "\n"); |
258 total_bytes - total_bytes_sent, 0); | 599 #endif |
259 | 600 |
260 if (bytes_sent < 0) { | 601 rc4 = purple_cipher_context_new_by_name("rc4", NULL); |
261 purple_debug_info("msim", "msim_send_raw(%s): send() failed: %s\n", | 602 |
262 buf, g_strerror(errno)); | 603 /* Note: 'key' variable is 0x14 bytes (from SHA-1 hash), |
263 return total_bytes_sent; | 604 * but only first 0x10 used for the RC4 key. */ |
264 } | 605 purple_cipher_context_set_option(rc4, "key_len", (gpointer)0x10); |
265 total_bytes_sent += bytes_sent; | 606 purple_cipher_context_set_key(rc4, key); |
266 | 607 |
267 } while(total_bytes_sent < total_bytes); | 608 /* TODO: obtain IPs of network interfaces */ |
268 | 609 |
269 return total_bytes_sent; | 610 /* rc4 encrypt: |
270 } | 611 * nonce1+email+IP list */ |
271 | 612 |
272 | 613 data = g_string_new(NULL); |
273 /** | 614 g_string_append_len(data, nonce, NONCE_SIZE); |
274 * Start logging in to the MSIM servers. | 615 g_string_append(data, email); |
275 * | 616 g_string_append_len(data, MSIM_LOGIN_IP_LIST, MSIM_LOGIN_IP_LIST_LEN); |
276 * @param acct Account information to use to login. | 617 |
277 */ | 618 data_out = g_new0(guchar, data->len); |
278 void | 619 |
279 msim_login(PurpleAccount *acct) | 620 purple_cipher_context_encrypt(rc4, (const guchar *)data->str, |
280 { | 621 data->len, data_out, &data_out_len); |
281 PurpleConnection *gc; | 622 purple_cipher_context_destroy(rc4); |
282 const gchar *host; | 623 |
283 int port; | 624 if (data_out_len != data->len) { |
284 | 625 purple_debug_info("msim", "msim_compute_login_response: " |
285 g_return_if_fail(acct != NULL); | 626 "data length mismatch: %" G_GSIZE_FORMAT " != %" |
286 g_return_if_fail(acct->username != NULL); | 627 G_GSIZE_FORMAT "\n", data_out_len, data->len); |
287 | 628 } |
288 purple_debug_info("msim", "logging in %s\n", acct->username); | 629 |
289 | 630 g_string_free(data, TRUE); |
290 gc = purple_account_get_connection(acct); | 631 |
291 gc->proto_data = msim_session_new(acct); | 632 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE |
292 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_NO_URLDESC; | 633 purple_debug_info("msim", "response=<%s>\n", data_out); |
293 | 634 #endif |
294 /* 1. connect to server */ | 635 |
295 purple_connection_update_progress(gc, _("Connecting"), | 636 *response_len = data_out_len; |
296 0, /* which connection step this is */ | 637 |
297 4); /* total number of steps */ | 638 return (gchar *)data_out; |
298 | 639 } |
299 host = purple_account_get_string(acct, "server", MSIM_SERVER); | 640 |
300 port = purple_account_get_int(acct, "port", MSIM_PORT); | 641 /** |
301 | 642 * Process a login challenge, sending a response. |
302 /* From purple.sf.net/api: | 643 * |
303 * """Note that this function name can be misleading--although it is called | 644 * @param session |
304 * "proxy connect," it is used for establishing any outgoing TCP connection, | |
305 * whether through a proxy or not.""" */ | |
306 | |
307 /* Calls msim_connect_cb when connected. */ | |
308 if (!purple_proxy_connect(gc, acct, host, port, msim_connect_cb, gc)) { | |
309 /* TODO: try other ports if in auto mode, then save | |
310 * working port and try that first next time. */ | |
311 purple_connection_error_reason (gc, | |
312 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, | |
313 _("Couldn't create socket")); | |
314 return; | |
315 } | |
316 } | |
317 | |
318 /** | |
319 * Process a login challenge, sending a response. | |
320 * | |
321 * @param session | |
322 * @param msg Login challenge message. | 645 * @param msg Login challenge message. |
323 * | 646 * |
324 * @return TRUE if successful, FALSE if not | 647 * @return TRUE if successful, FALSE if not |
325 */ | 648 */ |
326 static gboolean | 649 static gboolean |
327 msim_login_challenge(MsimSession *session, MsimMessage *msg) | 650 msim_login_challenge(MsimSession *session, MsimMessage *msg) |
328 { | 651 { |
329 PurpleAccount *account; | 652 PurpleAccount *account; |
330 gchar *response; | 653 gchar *response; |
331 guint response_len; | 654 guint response_len; |
332 gchar *nc; | 655 gchar *nc; |
381 | 704 |
382 return ret; | 705 return ret; |
383 } | 706 } |
384 | 707 |
385 /** | 708 /** |
386 * Compute the base64'd login challenge response based on username, password, nonce, and IPs. | 709 * Process unrecognized information. |
387 * | 710 * |
388 * @param nonce The base64 encoded nonce ('nc') field from the server. | 711 * @param session |
389 * @param email User's email address (used as login name). | 712 * @param msg An MsimMessage that was unrecognized, or NULL. |
390 * @param password User's cleartext password. | 713 * @param note Information on what was unrecognized, or NULL. |
391 * @param response_len Will be written with response length. | 714 */ |
392 * | 715 void |
393 * @return Binary login challenge response, ready to send to the server. | 716 msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note) |
394 * Must be g_free()'d when finished. NULL if error. | 717 { |
395 */ | 718 /* TODO: Some more context, outwardly equivalent to a backtrace, |
396 static gchar * | 719 * for helping figure out what this msg is for. What was going on? |
397 msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], | 720 * But not too much information so that a user |
398 const gchar *email, const gchar *password, guint *response_len) | 721 * posting this dump reveals confidential information. |
399 { | 722 */ |
400 PurpleCipherContext *key_context; | 723 |
401 PurpleCipher *sha1; | 724 /* TODO: dump unknown msgs to file, so user can send them to me |
402 PurpleCipherContext *rc4; | 725 * if they wish, to help add support for new messages (inspired |
403 | 726 * by Alexandr Shutko, who maintains OSCAR protocol documentation). |
404 guchar hash_pw[HASH_SIZE]; | 727 * |
405 guchar key[HASH_SIZE]; | 728 * Filed enhancement ticket for libpurple as #4688. |
406 gchar *password_utf16le, *password_utf8_lc; | 729 */ |
407 guchar *data; | 730 |
408 guchar *data_out; | 731 purple_debug_info("msim", "Unrecognized data on account for %s\n", |
409 size_t data_len, data_out_len; | 732 (session && session->account && session->account->username) ? |
410 gsize conv_bytes_read, conv_bytes_written; | 733 session->account->username : "(NULL)"); |
411 GError *conv_error; | 734 if (note) { |
412 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE | 735 purple_debug_info("msim", "(Note: %s)\n", note); |
413 int i; | 736 } |
737 | |
738 if (msg) { | |
739 msim_msg_dump("Unrecognized message dump: %s\n", msg); | |
740 } | |
741 } | |
742 | |
743 /** Called when the session key arrives to check whether the user | |
744 * has a username, and set one if desired. */ | |
745 static gboolean | |
746 msim_is_username_set(MsimSession *session, MsimMessage *msg) | |
747 { | |
748 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
749 g_return_val_if_fail(msg != NULL, FALSE); | |
750 g_return_val_if_fail(session->gc != NULL, FALSE); | |
751 | |
752 session->sesskey = msim_msg_get_integer(msg, "sesskey"); | |
753 purple_debug_info("msim", "SESSKEY=<%d>\n", session->sesskey); | |
754 | |
755 /* What is proof? Used to be uid, but now is 52 base64'd bytes... */ | |
756 | |
757 /* Comes with: proof,profileid,userid,uniquenick -- all same values | |
758 * some of the time, but can vary. This is our own user ID. */ | |
759 session->userid = msim_msg_get_integer(msg, "userid"); | |
760 | |
761 /* Save uid to account so this account can be looked up by uid. */ | |
762 purple_account_set_int(session->account, "uid", session->userid); | |
763 | |
764 /* Not sure what profileid is used for. */ | |
765 if (msim_msg_get_integer(msg, "profileid") != session->userid) { | |
766 msim_unrecognized(session, msg, | |
767 "Profile ID didn't match user ID, don't know why"); | |
768 } | |
769 | |
770 /* We now know are our own username, only after we're logged in.. | |
771 * which is weird, but happens because you login with your email | |
772 * address and not username. Will be freed in msim_session_destroy(). */ | |
773 session->username = msim_msg_get_string(msg, "uniquenick"); | |
774 | |
775 /* If user lacks a username, help them get one. */ | |
776 if (msim_msg_get_integer(msg, "uniquenick") == session->userid) { | |
777 purple_debug_info("msim_is_username_set", "no username is set\n"); | |
778 purple_request_yes_no(session->gc, | |
779 _("MySpaceIM - No Username Set"), | |
780 _("You appear to have no MySpace username."), | |
781 _("Would you like to set one now? (Note: THIS CANNOT BE CHANGED!)"), | |
782 0, | |
783 session->account, | |
784 NULL, | |
785 NULL, | |
786 session->gc, | |
787 G_CALLBACK(msim_set_username_cb), | |
788 G_CALLBACK(msim_do_not_set_username_cb)); | |
789 purple_debug_info("msim_is_username_set","'username not set' alert prompted\n"); | |
790 return FALSE; | |
791 } | |
792 return TRUE; | |
793 } | |
794 | |
795 #ifdef MSIM_USE_KEEPALIVE | |
796 /** | |
797 * Check if the connection is still alive, based on last communication. | |
798 */ | |
799 static gboolean | |
800 msim_check_alive(gpointer data) | |
801 { | |
802 MsimSession *session; | |
803 time_t delta; | |
804 | |
805 session = (MsimSession *)data; | |
806 | |
807 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
808 | |
809 delta = time(NULL) - session->last_comm; | |
810 | |
811 /* purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta); */ | |
812 if (delta >= MSIM_KEEPALIVE_INTERVAL) { | |
813 purple_debug_info("msim", | |
814 "msim_check_alive: %zu > interval of %d, presumed dead\n", | |
815 delta, MSIM_KEEPALIVE_INTERVAL); | |
816 purple_connection_error_reason(session->gc, | |
817 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, | |
818 _("Lost connection with server")); | |
819 | |
820 return FALSE; | |
821 } | |
822 | |
823 return TRUE; | |
824 } | |
414 #endif | 825 #endif |
415 | 826 |
416 g_return_val_if_fail(nonce != NULL, NULL); | 827 /** |
417 g_return_val_if_fail(email != NULL, NULL); | 828 * Handle mail reply checks. |
418 g_return_val_if_fail(password != NULL, NULL); | 829 */ |
419 g_return_val_if_fail(response_len != NULL, NULL); | 830 static void |
420 | 831 msim_check_inbox_cb(MsimSession *session, const MsimMessage *reply, gpointer data) |
421 /* Convert password to lowercase (required for passwords containing | 832 { |
422 * uppercase characters). MySpace passwords are lowercase, | 833 MsimMessage *body; |
423 * see ticket #2066. */ | 834 guint old_inbox_status; |
424 password_utf8_lc = g_utf8_strdown(password, -1); | 835 guint i, n; |
425 | 836 const gchar *froms[5], *tos[5], *urls[5], *subjects[5]; |
426 /* Convert ASCII password to UTF16 little endian */ | 837 |
427 purple_debug_info("msim", "converting password to UTF-16LE\n"); | 838 /* Information for each new inbox message type. */ |
428 conv_error = NULL; | 839 static struct |
429 password_utf16le = g_convert(password_utf8_lc, -1, "UTF-16LE", "UTF-8", | 840 { |
430 &conv_bytes_read, &conv_bytes_written, &conv_error); | 841 const gchar *key; |
431 g_free(password_utf8_lc); | 842 guint bit; |
432 | 843 const gchar *url; |
433 g_return_val_if_fail(conv_bytes_read == strlen(password), NULL); | 844 const gchar *text; |
434 | 845 } message_types[] = { |
435 if (conv_error != NULL) { | 846 { "Mail", MSIM_INBOX_MAIL, "http://messaging.myspace.com/index.cfm?fuseaction=mail.inbox", NULL }, |
436 purple_debug_error("msim", | 847 { "BlogComment", MSIM_INBOX_BLOG_COMMENT, "http://blog.myspace.com/index.cfm?fuseaction=blog", NULL }, |
437 "g_convert password UTF8->UTF16LE failed: %s", | 848 { "ProfileComment", MSIM_INBOX_PROFILE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL }, |
438 conv_error->message); | 849 { "FriendRequest", MSIM_INBOX_FRIEND_REQUEST, "http://messaging.myspace.com/index.cfm?fuseaction=mail.friendRequests", NULL }, |
439 g_error_free(conv_error); | 850 { "PictureComment", MSIM_INBOX_PICTURE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL } |
440 return NULL; | 851 }; |
441 } | 852 |
442 | 853 /* Can't write _()'d strings in array initializers. Workaround. */ |
443 /* Compute password hash */ | 854 message_types[0].text = _("New mail messages"); |
444 purple_cipher_digest_region("sha1", (guchar *)password_utf16le, | 855 message_types[1].text = _("New blog comments"); |
445 conv_bytes_written, sizeof(hash_pw), hash_pw, NULL); | 856 message_types[2].text = _("New profile comments"); |
446 g_free(password_utf16le); | 857 message_types[3].text = _("New friend requests!"); |
447 | 858 message_types[4].text = _("New picture comments"); |
448 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE | 859 |
449 purple_debug_info("msim", "pwhash = "); | 860 g_return_if_fail(reply != NULL); |
450 for (i = 0; i < sizeof(hash_pw); i++) | 861 |
451 purple_debug_info("msim", "%.2x ", hash_pw[i]); | 862 body = msim_msg_get_dictionary(reply, "body"); |
452 purple_debug_info("msim", "\n"); | 863 |
864 if (body == NULL) | |
865 return; | |
866 | |
867 old_inbox_status = session->inbox_status; | |
868 | |
869 n = 0; | |
870 | |
871 for (i = 0; i < sizeof(message_types) / sizeof(message_types[0]); ++i) { | |
872 const gchar *key; | |
873 guint bit; | |
874 | |
875 key = message_types[i].key; | |
876 bit = message_types[i].bit; | |
877 | |
878 if (msim_msg_get(body, key)) { | |
879 /* Notify only on when _changes_ from no mail -> has mail | |
880 * (edge triggered) */ | |
881 if (!(session->inbox_status & bit)) { | |
882 purple_debug_info("msim", "msim_check_inbox_cb: got %s, at %d\n", | |
883 key ? key : "(NULL)", n); | |
884 | |
885 subjects[n] = message_types[i].text; | |
886 froms[n] = _("MySpace"); | |
887 tos[n] = session->username; | |
888 /* TODO: append token, web challenge, so automatically logs in. | |
889 * Would also need to free strings because they won't be static | |
890 */ | |
891 urls[n] = message_types[i].url; | |
892 | |
893 ++n; | |
894 } else { | |
895 purple_debug_info("msim", | |
896 "msim_check_inbox_cb: already notified of %s\n", | |
897 key ? key : "(NULL)"); | |
898 } | |
899 | |
900 session->inbox_status |= bit; | |
901 } | |
902 } | |
903 | |
904 if (n) { | |
905 purple_debug_info("msim", | |
906 "msim_check_inbox_cb: notifying of %d\n", n); | |
907 | |
908 /* TODO: free strings with callback _if_ change to dynamic (w/ token) */ | |
909 purple_notify_emails(session->gc, /* handle */ | |
910 n, /* count */ | |
911 TRUE, /* detailed */ | |
912 subjects, froms, tos, urls, | |
913 NULL, /* PurpleNotifyCloseCallback cb */ | |
914 NULL); /* gpointer user_data */ | |
915 | |
916 } | |
917 | |
918 msim_msg_free(body); | |
919 } | |
920 | |
921 /** | |
922 * Send request to check if there is new mail. | |
923 */ | |
924 static gboolean | |
925 msim_check_inbox(gpointer data) | |
926 { | |
927 MsimSession *session; | |
928 | |
929 session = (MsimSession *)data; | |
930 | |
931 if (!MSIM_SESSION_VALID(session)) { | |
932 purple_debug_info("msim", "msim_check_inbox: session invalid, stopping the mail check.\n"); | |
933 return FALSE; | |
934 } | |
935 | |
936 purple_debug_info("msim", "msim_check_inbox: checking mail\n"); | |
937 g_return_val_if_fail(msim_send(session, | |
938 "persist", MSIM_TYPE_INTEGER, 1, | |
939 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
940 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET, | |
941 "dsn", MSIM_TYPE_INTEGER, MG_CHECK_MAIL_DSN, | |
942 "lid", MSIM_TYPE_INTEGER, MG_CHECK_MAIL_LID, | |
943 "uid", MSIM_TYPE_INTEGER, session->userid, | |
944 "rid", MSIM_TYPE_INTEGER, | |
945 msim_new_reply_callback(session, msim_check_inbox_cb, NULL), | |
946 "body", MSIM_TYPE_STRING, g_strdup(""), | |
947 NULL), TRUE); | |
948 | |
949 /* Always return true, so that we keep checking for mail. */ | |
950 return TRUE; | |
951 } | |
952 | |
953 /** | |
954 * Add contact from server to buddy list, after looking up username. | |
955 * Callback from msim_add_contact_from_server(). | |
956 * | |
957 * @param data An MsimMessage * of the contact information. Will be freed. | |
958 */ | |
959 static void | |
960 msim_add_contact_from_server_cb(MsimSession *session, const MsimMessage *user_lookup_info, gpointer data) | |
961 { | |
962 MsimMessage *contact_info, *user_lookup_info_body; | |
963 PurpleGroup *group; | |
964 PurpleBuddy *buddy; | |
965 MsimUser *user; | |
966 gchar *username, *group_name, *display_name; | |
967 guint uid, visibility; | |
968 | |
969 contact_info = (MsimMessage *)data; | |
970 purple_debug_info("msim_add_contact_from_server_cb", "contact_info addr=%p\n", contact_info); | |
971 uid = msim_msg_get_integer(contact_info, "ContactID"); | |
972 | |
973 if (!user_lookup_info) { | |
974 username = g_strdup(msim_uid2username_from_blist(session->account, uid)); | |
975 display_name = NULL; | |
976 g_return_if_fail(username != NULL); | |
977 } else { | |
978 user_lookup_info_body = msim_msg_get_dictionary(user_lookup_info, "body"); | |
979 username = msim_msg_get_string(user_lookup_info_body, "UserName"); | |
980 display_name = msim_msg_get_string(user_lookup_info_body, "DisplayName"); | |
981 msim_msg_free(user_lookup_info_body); | |
982 g_return_if_fail(username != NULL); | |
983 } | |
984 | |
985 purple_debug_info("msim_add_contact_from_server_cb", | |
986 "*** about to add/update username=%s\n", username); | |
987 | |
988 /* 1. Creates a new group, or gets existing group if it exists (or so | |
989 * the documentation claims). */ | |
990 group_name = msim_msg_get_string(contact_info, "GroupName"); | |
991 if (!group_name || (*group_name == '\0')) { | |
992 g_free(group_name); | |
993 group_name = g_strdup(_("IM Friends")); | |
994 purple_debug_info("myspace", "No GroupName specified, defaulting to '%s'.\n", group_name); | |
995 } | |
996 group = purple_find_group(group_name); | |
997 if (!group) { | |
998 group = purple_group_new(group_name); | |
999 /* Add group to beginning. See #2752. */ | |
1000 purple_blist_add_group(group, NULL); | |
1001 } | |
1002 g_free(group_name); | |
1003 | |
1004 visibility = msim_msg_get_integer(contact_info, "Visibility"); | |
1005 if (visibility == 2) { | |
1006 /* This buddy is blocked (and therefore not on our buddy list */ | |
1007 purple_privacy_deny_add(session->account, username, TRUE); | |
1008 msim_msg_free(contact_info); | |
1009 g_free(username); | |
1010 g_free(display_name); | |
1011 return; | |
1012 } | |
1013 | |
1014 /* 2. Get or create buddy */ | |
1015 buddy = purple_find_buddy(session->account, username); | |
1016 if (!buddy) { | |
1017 purple_debug_info("msim_add_contact_from_server_cb", | |
1018 "creating new buddy: %s\n", username); | |
1019 buddy = purple_buddy_new(session->account, username, NULL); | |
1020 } | |
1021 | |
1022 /* TODO: use 'Position' in contact_info to take into account where buddy is */ | |
1023 purple_blist_add_buddy(buddy, NULL, group, NULL /* insertion point */); | |
1024 | |
1025 if (strtol(username, NULL, 10) == uid) { | |
1026 /* | |
1027 * This user has not set their username! Set their server | |
1028 * alias to their display name so that we don't see a bunch | |
1029 * of numbers in the buddy list. | |
1030 */ | |
1031 if (display_name != NULL) { | |
1032 purple_blist_node_set_string(&buddy->node, "DisplayName", display_name); | |
1033 serv_got_alias(session->gc, username, display_name); | |
1034 } else { | |
1035 serv_got_alias(session->gc, username, | |
1036 purple_blist_node_get_string(&buddy->node, "DisplayName")); | |
1037 } | |
1038 } | |
1039 g_free(display_name); | |
1040 | |
1041 /* 3. Update buddy information */ | |
1042 user = msim_get_user_from_buddy(buddy); | |
1043 | |
1044 user->id = uid; | |
1045 /* Keep track of the user ID across sessions */ | |
1046 purple_blist_node_set_int(&buddy->node, "UserID", uid); | |
1047 | |
1048 /* Stores a few fields in the MsimUser, relevant to the buddy itself. | |
1049 * AvatarURL, Headline, ContactID. */ | |
1050 msim_store_user_info(session, contact_info, NULL); | |
1051 | |
1052 /* TODO: other fields, store in 'user' */ | |
1053 msim_msg_free(contact_info); | |
1054 | |
1055 g_free(username); | |
1056 } | |
1057 | |
1058 /** | |
1059 * Add first ContactID in contact_info to buddy's list. Used to add | |
1060 * server-side buddies to client-side list. | |
1061 * | |
1062 * @return TRUE if added. | |
1063 */ | |
1064 static gboolean | |
1065 msim_add_contact_from_server(MsimSession *session, MsimMessage *contact_info) | |
1066 { | |
1067 guint uid; | |
1068 const gchar *username; | |
1069 | |
1070 uid = msim_msg_get_integer(contact_info, "ContactID"); | |
1071 g_return_val_if_fail(uid != 0, FALSE); | |
1072 | |
1073 /* Lookup the username, since NickName and IMName is unreliable */ | |
1074 username = msim_uid2username_from_blist(session->account, uid); | |
1075 if (!username) { | |
1076 gchar *uid_str; | |
1077 | |
1078 uid_str = g_strdup_printf("%d", uid); | |
1079 purple_debug_info("msim_add_contact_from_server", | |
1080 "contact_info addr=%p\n", contact_info); | |
1081 msim_lookup_user(session, uid_str, msim_add_contact_from_server_cb, (gpointer)msim_msg_clone(contact_info)); | |
1082 g_free(uid_str); | |
1083 } else { | |
1084 msim_add_contact_from_server_cb(session, NULL, (gpointer)msim_msg_clone(contact_info)); | |
1085 } | |
1086 | |
1087 /* Say that the contact was added, even if we're still looking up | |
1088 * their username. */ | |
1089 return TRUE; | |
1090 } | |
1091 | |
1092 /** | |
1093 * Called when contact list is received from server. | |
1094 */ | |
1095 static void | |
1096 msim_got_contact_list(MsimSession *session, const MsimMessage *reply, gpointer user_data) | |
1097 { | |
1098 MsimMessage *body, *body_node; | |
1099 gchar *msg; | |
1100 guint buddy_count; | |
1101 | |
1102 body = msim_msg_get_dictionary(reply, "body"); | |
1103 if (!body) { | |
1104 /* No friends. Not an error. */ | |
1105 return; | |
1106 } | |
1107 | |
1108 buddy_count = 0; | |
1109 | |
1110 for (body_node = body; | |
1111 body_node != NULL; | |
1112 body_node = msim_msg_get_next_element_node(body_node)) | |
1113 { | |
1114 MsimMessageElement *elem; | |
1115 | |
1116 elem = (MsimMessageElement *)body_node->data; | |
1117 | |
1118 if (g_str_equal(elem->name, "ContactID")) | |
1119 { | |
1120 /* Will look for first contact in body_node */ | |
1121 if (msim_add_contact_from_server(session, body_node)) { | |
1122 ++buddy_count; | |
1123 } | |
1124 } | |
1125 } | |
1126 | |
1127 switch (GPOINTER_TO_UINT(user_data)) { | |
1128 case MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS: | |
1129 msg = g_strdup_printf(ngettext("%d buddy was added or updated from the server (including buddies already on the server-side list)", | |
1130 "%d buddies were added or updated from the server (including buddies already on the server-side list)", | |
1131 buddy_count), | |
1132 buddy_count); | |
1133 purple_notify_info(session->account, _("Add contacts from server"), msg, NULL); | |
1134 g_free(msg); | |
1135 break; | |
1136 | |
1137 case MSIM_CONTACT_LIST_IMPORT_TOP_FRIENDS: | |
1138 /* TODO */ | |
1139 break; | |
1140 | |
1141 case MSIM_CONTACT_LIST_INITIAL_FRIENDS: | |
1142 /* Nothing */ | |
1143 break; | |
1144 } | |
1145 | |
1146 msim_msg_free(body); | |
1147 } | |
1148 | |
1149 /** | |
1150 * Get contact list, calling msim_got_contact_list() with | |
1151 * what_to_do_after as user_data gpointer. | |
1152 * | |
1153 * @param what_to_do_after should be one of the MSIM_CONTACT_LIST_* #defines. | |
1154 */ | |
1155 static gboolean | |
1156 msim_get_contact_list(MsimSession *session, int what_to_do_after) | |
1157 { | |
1158 return msim_send(session, | |
1159 "persist", MSIM_TYPE_INTEGER, 1, | |
1160 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
1161 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET, | |
1162 "dsn", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_DSN, | |
1163 "lid", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_LID, | |
1164 "uid", MSIM_TYPE_INTEGER, session->userid, | |
1165 "rid", MSIM_TYPE_INTEGER, | |
1166 msim_new_reply_callback(session, msim_got_contact_list, GUINT_TO_POINTER(what_to_do_after)), | |
1167 "body", MSIM_TYPE_STRING, g_strdup(""), | |
1168 NULL); | |
1169 } | |
1170 | |
1171 /** Called after username is set, if necessary and we're open for business. */ | |
1172 gboolean msim_we_are_logged_on(MsimSession *session) | |
1173 { | |
1174 MsimMessage *body; | |
1175 | |
1176 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
1177 | |
1178 /* Set display name to username (otherwise will show email address) */ | |
1179 purple_connection_set_display_name(session->gc, session->username); | |
1180 | |
1181 /* The session is now set up, ready to be connected. This emits the | |
1182 * signedOn signal, so clients can now do anything with msimprpl, and | |
1183 * we're ready for it (session key, userid, username all setup). */ | |
1184 purple_connection_update_progress(session->gc, _("Connected"), 3, 4); | |
1185 purple_connection_set_state(session->gc, PURPLE_CONNECTED); | |
1186 | |
1187 body = msim_msg_new( | |
1188 "UserID", MSIM_TYPE_INTEGER, session->userid, | |
1189 NULL); | |
1190 | |
1191 /* Request IM info about ourself. */ | |
1192 msim_send(session, | |
1193 "persist", MSIM_TYPE_INTEGER, 1, | |
1194 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
1195 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET, | |
1196 "dsn", MSIM_TYPE_INTEGER, MG_OWN_MYSPACE_INFO_DSN, | |
1197 "lid", MSIM_TYPE_INTEGER, MG_OWN_MYSPACE_INFO_LID, | |
1198 "rid", MSIM_TYPE_INTEGER, session->next_rid++, | |
1199 "UserID", MSIM_TYPE_INTEGER, session->userid, | |
1200 "body", MSIM_TYPE_DICTIONARY, body, | |
1201 NULL); | |
1202 | |
1203 /* Request MySpace info about ourself. */ | |
1204 msim_send(session, | |
1205 "persist", MSIM_TYPE_INTEGER, 1, | |
1206 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
1207 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET, | |
1208 "dsn", MSIM_TYPE_INTEGER, MG_OWN_IM_INFO_DSN, | |
1209 "lid", MSIM_TYPE_INTEGER, MG_OWN_IM_INFO_LID, | |
1210 "rid", MSIM_TYPE_INTEGER, session->next_rid++, | |
1211 "body", MSIM_TYPE_STRING, g_strdup(""), | |
1212 NULL); | |
1213 | |
1214 /* TODO: set options (persist cmd=514,dsn=1,lid=10) */ | |
1215 /* TODO: set blocklist */ | |
1216 | |
1217 /* Notify servers of our current status. */ | |
1218 purple_debug_info("msim", "msim_we_are_logged_on: notifying servers of status\n"); | |
1219 msim_set_status(session->account, | |
1220 purple_account_get_active_status(session->account)); | |
1221 | |
1222 /* TODO: setinfo */ | |
1223 /* | |
1224 body = msim_msg_new( | |
1225 "TotalFriends", MSIM_TYPE_INTEGER, 666, | |
1226 NULL); | |
1227 msim_send(session, | |
1228 "setinfo", MSIM_TYPE_BOOLEAN, TRUE, | |
1229 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
1230 "info", MSIM_TYPE_DICTIONARY, body, | |
1231 NULL); | |
1232 */ | |
1233 | |
1234 /* Disable due to problems with timeouts. TODO: fix. */ | |
1235 #ifdef MSIM_USE_KEEPALIVE | |
1236 purple_timeout_add(MSIM_KEEPALIVE_INTERVAL_CHECK, | |
1237 (GSourceFunc)msim_check_alive, session); | |
453 #endif | 1238 #endif |
454 | 1239 |
455 /* key = sha1(sha1(pw) + nonce2) */ | 1240 /* Check mail if they want to. */ |
456 sha1 = purple_ciphers_find_cipher("sha1"); | 1241 if (purple_account_get_check_mail(session->account)) { |
457 key_context = purple_cipher_context_new(sha1, NULL); | 1242 session->inbox_handle = purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK, |
458 purple_cipher_context_append(key_context, hash_pw, HASH_SIZE); | 1243 (GSourceFunc)msim_check_inbox, session); |
459 purple_cipher_context_append(key_context, (guchar *)(nonce + NONCE_SIZE), NONCE_SIZE); | 1244 msim_check_inbox(session); |
460 purple_cipher_context_digest(key_context, sizeof(key), key, NULL); | 1245 } |
461 purple_cipher_context_destroy(key_context); | 1246 |
462 | 1247 msim_get_contact_list(session, MSIM_CONTACT_LIST_INITIAL_FRIENDS); |
463 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE | 1248 |
464 purple_debug_info("msim", "key = "); | 1249 return TRUE; |
465 for (i = 0; i < sizeof(key); i++) { | 1250 } |
466 purple_debug_info("msim", "%.2x ", key[i]); | 1251 |
467 } | 1252 /** |
468 purple_debug_info("msim", "\n"); | 1253 * Record the client version in the buddy list, from an incoming message. |
469 #endif | 1254 */ |
470 | |
471 rc4 = purple_cipher_context_new_by_name("rc4", NULL); | |
472 | |
473 /* Note: 'key' variable is 0x14 bytes (from SHA-1 hash), | |
474 * but only first 0x10 used for the RC4 key. */ | |
475 purple_cipher_context_set_option(rc4, "key_len", (gpointer)0x10); | |
476 purple_cipher_context_set_key(rc4, key); | |
477 | |
478 /* TODO: obtain IPs of network interfaces */ | |
479 | |
480 /* rc4 encrypt: | |
481 * nonce1+email+IP list */ | |
482 | |
483 data_len = NONCE_SIZE + strlen(email) + MSIM_LOGIN_IP_LIST_LEN; | |
484 data = g_new0(guchar, data_len); | |
485 memcpy(data, nonce, NONCE_SIZE); | |
486 memcpy(data + NONCE_SIZE, email, strlen(email)); | |
487 memcpy(data + NONCE_SIZE + strlen(email), MSIM_LOGIN_IP_LIST, MSIM_LOGIN_IP_LIST_LEN); | |
488 | |
489 data_out = g_new0(guchar, data_len); | |
490 | |
491 purple_cipher_context_encrypt(rc4, (const guchar *)data, | |
492 data_len, data_out, &data_out_len); | |
493 purple_cipher_context_destroy(rc4); | |
494 g_free(data); | |
495 | |
496 if (data_out_len != data_len) { | |
497 purple_debug_info("msim", "msim_compute_login_response: " | |
498 "data length mismatch: %" G_GSIZE_FORMAT " != %" | |
499 G_GSIZE_FORMAT "\n", data_out_len, data_len); | |
500 } | |
501 | |
502 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE | |
503 purple_debug_info("msim", "response=<%s>\n", data_out); | |
504 #endif | |
505 | |
506 *response_len = data_out_len; | |
507 | |
508 return (gchar *)data_out; | |
509 } | |
510 | |
511 /** | |
512 * Schedule an IM to be sent once the user ID is looked up. | |
513 * | |
514 * @param gc Connection. | |
515 * @param who A user id, email, or username to send the message to. | |
516 * @param message Instant message text to send. | |
517 * @param flags Flags. | |
518 * | |
519 * @return 1 if successful or postponed, -1 if failed | |
520 * | |
521 * Allows sending to a user by username, email address, or userid. If | |
522 * a username or email address is given, the userid must be looked up. | |
523 * This function does that by calling msim_postprocess_outgoing(). | |
524 */ | |
525 int | |
526 msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, | |
527 PurpleMessageFlags flags) | |
528 { | |
529 MsimSession *session; | |
530 gchar *message_msim; | |
531 int rc; | |
532 | |
533 g_return_val_if_fail(gc != NULL, -1); | |
534 g_return_val_if_fail(who != NULL, -1); | |
535 g_return_val_if_fail(message != NULL, -1); | |
536 | |
537 /* 'flags' has many options, not used here. */ | |
538 | |
539 session = (MsimSession *)gc->proto_data; | |
540 | |
541 g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); | |
542 | |
543 message_msim = html_to_msim_markup(session, message); | |
544 | |
545 if (msim_send_bm(session, who, message_msim, MSIM_BM_INSTANT)) { | |
546 /* Return 1 to have Purple show this IM as being sent, 0 to not. I always | |
547 * return 1 even if the message could not be sent, since I don't know if | |
548 * it has failed yet--because the IM is only sent after the userid is | |
549 * retrieved from the server (which happens after this function returns). | |
550 * If an error does occur, it should be logged to the IM window. | |
551 */ | |
552 rc = 1; | |
553 } else { | |
554 rc = -1; | |
555 } | |
556 | |
557 g_free(message_msim); | |
558 | |
559 return rc; | |
560 } | |
561 | |
562 /** Send a buddy message of a given type. | |
563 * | |
564 * @param session | |
565 * @param who Username to send message to. | |
566 * @param text Message text to send. Not freed; will be copied. | |
567 * @param type A MSIM_BM_* constant. | |
568 * | |
569 * @return TRUE if success, FALSE if fail. | |
570 * | |
571 * Buddy messages ('bm') include instant messages, action messages, status messages, etc. | |
572 * | |
573 */ | |
574 gboolean | |
575 msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, | |
576 int type) | |
577 { | |
578 gboolean rc; | |
579 MsimMessage *msg; | |
580 const gchar *from_username; | |
581 | |
582 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
583 g_return_val_if_fail(who != NULL, FALSE); | |
584 g_return_val_if_fail(text != NULL, FALSE); | |
585 | |
586 from_username = session->account->username; | |
587 | |
588 g_return_val_if_fail(from_username != NULL, FALSE); | |
589 | |
590 purple_debug_info("msim", "sending %d message from %s to %s: %s\n", | |
591 type, from_username, who, text); | |
592 | |
593 msg = msim_msg_new( | |
594 "bm", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(type), | |
595 "sesskey", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(session->sesskey), | |
596 /* 't' will be inserted here */ | |
597 "cv", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(MSIM_CLIENT_VERSION), | |
598 "msg", MSIM_TYPE_STRING, g_strdup(text), | |
599 NULL); | |
600 | |
601 rc = msim_postprocess_outgoing(session, msg, who, "t", "cv"); | |
602 | |
603 msim_msg_free(msg); | |
604 | |
605 return rc; | |
606 } | |
607 | |
608 | |
609 /** Record the client version in the buddy list, from an incoming message. */ | |
610 static gboolean | 1255 static gboolean |
611 msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg) | 1256 msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg) |
612 { | 1257 { |
613 gchar *username, *cv; | 1258 gchar *username, *cv; |
614 gboolean ret; | 1259 gboolean ret; |
637 g_free(cv); | 1282 g_free(cv); |
638 | 1283 |
639 return ret; | 1284 return ret; |
640 } | 1285 } |
641 | 1286 |
642 /** Handle an incoming buddy message. */ | 1287 #ifdef MSIM_SEND_CLIENT_VERSION |
1288 /** | |
1289 * Send our client version to another unofficial client that understands it. | |
1290 */ | |
1291 static gboolean | |
1292 msim_send_unofficial_client(MsimSession *session, gchar *username) | |
1293 { | |
1294 gchar *our_info; | |
1295 gboolean ret; | |
1296 | |
1297 our_info = g_strdup_printf("Libpurple %d.%d.%d - msimprpl %s", | |
1298 PURPLE_MAJOR_VERSION, | |
1299 PURPLE_MINOR_VERSION, | |
1300 PURPLE_MICRO_VERSION, | |
1301 MSIM_PRPL_VERSION_STRING); | |
1302 | |
1303 ret = msim_send_bm(session, username, our_info, MSIM_BM_UNOFFICIAL_CLIENT); | |
1304 | |
1305 return ret; | |
1306 } | |
1307 #endif | |
1308 | |
1309 /** | |
1310 * Process incoming status messages. | |
1311 * | |
1312 * @param session | |
1313 * @param msg Status update message. Caller frees. | |
1314 * | |
1315 * @return TRUE if successful. | |
1316 */ | |
1317 static gboolean | |
1318 msim_incoming_status(MsimSession *session, MsimMessage *msg) | |
1319 { | |
1320 PurpleBuddyList *blist; | |
1321 MsimUser *user; | |
1322 GList *list; | |
1323 gchar *status_headline, *status_headline_escaped; | |
1324 gint status_code, purple_status_code; | |
1325 gchar *username; | |
1326 gchar *unrecognized_msg; | |
1327 | |
1328 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
1329 g_return_val_if_fail(msg != NULL, FALSE); | |
1330 | |
1331 /* Helpfully looked up by msim_incoming_resolve() for us. */ | |
1332 username = msim_msg_get_string(msg, "_username"); | |
1333 g_return_val_if_fail(username != NULL, FALSE); | |
1334 | |
1335 { | |
1336 gchar *ss; | |
1337 | |
1338 ss = msim_msg_get_string(msg, "msg"); | |
1339 purple_debug_info("msim", | |
1340 "msim_status: updating status for <%s> to <%s>\n", | |
1341 username, ss ? ss : "(NULL)"); | |
1342 g_free(ss); | |
1343 } | |
1344 | |
1345 /* Example fields: | |
1346 * |s|0|ss|Offline | |
1347 * |s|1|ss|:-)|ls||ip|0|p|0 | |
1348 */ | |
1349 list = msim_msg_get_list(msg, "msg"); | |
1350 | |
1351 status_code = msim_msg_get_integer_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE)); | |
1352 purple_debug_info("msim", "msim_status: %s's status code = %d\n", username, status_code); | |
1353 status_headline = msim_msg_get_string_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE)); | |
1354 | |
1355 blist = purple_get_blist(); | |
1356 | |
1357 /* Add buddy if not found. | |
1358 * TODO: Could this be responsible for #3444? */ | |
1359 user = msim_find_user(session, username); | |
1360 if (!user) { | |
1361 PurpleBuddy *buddy; | |
1362 | |
1363 purple_debug_info("msim", | |
1364 "msim_status: making new buddy for %s\n", username); | |
1365 buddy = purple_buddy_new(session->account, username, NULL); | |
1366 purple_blist_add_buddy(buddy, NULL, NULL, NULL); | |
1367 | |
1368 user = msim_get_user_from_buddy(buddy); | |
1369 user->id = msim_msg_get_integer(msg, "f"); | |
1370 | |
1371 /* Keep track of the user ID across sessions */ | |
1372 purple_blist_node_set_int(&buddy->node, "UserID", user->id); | |
1373 | |
1374 msim_store_user_info(session, msg, NULL); | |
1375 } else { | |
1376 purple_debug_info("msim", "msim_status: found buddy %s\n", username); | |
1377 } | |
1378 | |
1379 if (status_headline && strcmp(status_headline, "") != 0) { | |
1380 /* The status headline is plaintext, but libpurple treats it as HTML, | |
1381 * so escape any HTML characters to their entity equivalents. */ | |
1382 status_headline_escaped = g_markup_escape_text(status_headline, -1); | |
1383 } else { | |
1384 status_headline_escaped = NULL; | |
1385 } | |
1386 | |
1387 g_free(status_headline); | |
1388 | |
1389 /* don't copy; let the MsimUser own the headline, memory-wise */ | |
1390 g_free(user->headline); | |
1391 user->headline = status_headline_escaped; | |
1392 | |
1393 /* Set user status */ | |
1394 switch (status_code) { | |
1395 case MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN: | |
1396 purple_status_code = PURPLE_STATUS_OFFLINE; | |
1397 break; | |
1398 | |
1399 case MSIM_STATUS_CODE_ONLINE: | |
1400 purple_status_code = PURPLE_STATUS_AVAILABLE; | |
1401 break; | |
1402 | |
1403 case MSIM_STATUS_CODE_AWAY: | |
1404 purple_status_code = PURPLE_STATUS_AWAY; | |
1405 break; | |
1406 | |
1407 case MSIM_STATUS_CODE_IDLE: | |
1408 /* Treat idle as an available status. */ | |
1409 purple_status_code = PURPLE_STATUS_AVAILABLE; | |
1410 break; | |
1411 | |
1412 default: | |
1413 purple_debug_info("msim", "msim_incoming_status for %s, unknown status code %d, treating as available\n", | |
1414 username, status_code); | |
1415 purple_status_code = PURPLE_STATUS_AVAILABLE; | |
1416 | |
1417 unrecognized_msg = g_strdup_printf("msim_incoming_status, unrecognized status code: %d\n", | |
1418 status_code); | |
1419 msim_unrecognized(session, NULL, unrecognized_msg); | |
1420 g_free(unrecognized_msg); | |
1421 } | |
1422 | |
1423 purple_prpl_got_user_status(session->account, username, purple_primitive_get_id_from_type(purple_status_code), NULL); | |
1424 | |
1425 if (status_code == MSIM_STATUS_CODE_IDLE) { | |
1426 purple_debug_info("msim", "msim_status: got idle: %s\n", username); | |
1427 purple_prpl_got_user_idle(session->account, username, TRUE, 0); | |
1428 } else { | |
1429 /* All other statuses indicate going back to non-idle. */ | |
1430 purple_prpl_got_user_idle(session->account, username, FALSE, 0); | |
1431 } | |
1432 | |
1433 #ifdef MSIM_SEND_CLIENT_VERSION | |
1434 if (status_code == MSIM_STATUS_CODE_ONLINE) { | |
1435 /* Secretly whisper to unofficial clients our own version as they come online */ | |
1436 msim_send_unofficial_client(session, username); | |
1437 } | |
1438 #endif | |
1439 | |
1440 if (status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN) { | |
1441 /* Get information when they come online. | |
1442 * TODO: periodically refresh? | |
1443 */ | |
1444 purple_debug_info("msim_incoming_status", "%s came online, looking up\n", username); | |
1445 msim_lookup_user(session, username, NULL, NULL); | |
1446 } | |
1447 | |
1448 g_free(username); | |
1449 msim_msg_list_free(list); | |
1450 | |
1451 return TRUE; | |
1452 } | |
1453 | |
1454 /** | |
1455 * Handle an incoming instant message. | |
1456 * | |
1457 * @param session The session | |
1458 * @param msg Message from the server, containing 'f' (userid from) and 'msg'. | |
1459 * Should also contain username in _username from preprocessing. | |
1460 * | |
1461 * @return TRUE if successful. | |
1462 */ | |
1463 static gboolean | |
1464 msim_incoming_im(MsimSession *session, MsimMessage *msg) | |
1465 { | |
1466 gchar *username, *msg_msim_markup, *msg_purple_markup; | |
1467 gchar *userid; | |
1468 time_t time_received; | |
1469 PurpleConversation *conv; | |
1470 | |
1471 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
1472 g_return_val_if_fail(msg != NULL, FALSE); | |
1473 | |
1474 username = msim_msg_get_string(msg, "_username"); | |
1475 /* I know this isn't really a string... but we need it to be one for | |
1476 * purple_find_conversation_with_account(). */ | |
1477 userid = msim_msg_get_string(msg, "f"); | |
1478 g_return_val_if_fail(username != NULL, FALSE); | |
1479 | |
1480 purple_debug_info("msim_incoming_im", "UserID is %s", userid); | |
1481 | |
1482 if (msim_is_userid(username)) { | |
1483 purple_debug_info("msim", "Ignoring message from spambot (%s) on account %s\n", | |
1484 username, purple_account_get_username(session->account)); | |
1485 g_free(username); | |
1486 return FALSE; | |
1487 } | |
1488 | |
1489 /* See if a conversation with their UID already exists...*/ | |
1490 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, userid, session->account); | |
1491 if (conv) { | |
1492 /* Since the conversation exists... We need to normalize it */ | |
1493 purple_conversation_set_name(conv, username); | |
1494 } | |
1495 | |
1496 msg_msim_markup = msim_msg_get_string(msg, "msg"); | |
1497 g_return_val_if_fail(msg_msim_markup != NULL, FALSE); | |
1498 | |
1499 msg_purple_markup = msim_markup_to_html(session, msg_msim_markup); | |
1500 g_free(msg_msim_markup); | |
1501 | |
1502 time_received = msim_msg_get_integer(msg, "date"); | |
1503 if (!time_received) { | |
1504 purple_debug_info("msim_incoming_im", "date in message not set.\n"); | |
1505 time_received = time(NULL); | |
1506 } | |
1507 | |
1508 serv_got_im(session->gc, username, msg_purple_markup, PURPLE_MESSAGE_RECV, time_received); | |
1509 | |
1510 g_free(username); | |
1511 g_free(msg_purple_markup); | |
1512 | |
1513 return TRUE; | |
1514 } | |
1515 | |
1516 /** | |
1517 * Handle an incoming action message. | |
1518 * | |
1519 * @param session | |
1520 * @param msg | |
1521 * | |
1522 * @return TRUE if successful. | |
1523 */ | |
1524 static gboolean | |
1525 msim_incoming_action(MsimSession *session, MsimMessage *msg) | |
1526 { | |
1527 gchar *msg_text, *username; | |
1528 gboolean rc; | |
1529 | |
1530 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
1531 g_return_val_if_fail(msg != NULL, FALSE); | |
1532 | |
1533 msg_text = msim_msg_get_string(msg, "msg"); | |
1534 g_return_val_if_fail(msg_text != NULL, FALSE); | |
1535 | |
1536 username = msim_msg_get_string(msg, "_username"); | |
1537 g_return_val_if_fail(username != NULL, FALSE); | |
1538 | |
1539 purple_debug_info("msim", "msim_incoming_action: action <%s> from <%s>\n", | |
1540 msg_text, username); | |
1541 | |
1542 if (g_str_equal(msg_text, "%typing%")) { | |
1543 serv_got_typing(session->gc, username, 0, PURPLE_TYPING); | |
1544 rc = TRUE; | |
1545 } else if (g_str_equal(msg_text, "%stoptyping%")) { | |
1546 serv_got_typing_stopped(session->gc, username); | |
1547 rc = TRUE; | |
1548 } else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_")) { | |
1549 rc = msim_incoming_zap(session, msg); | |
1550 } else if (strstr(msg_text, "!!!GroupCount=")) { | |
1551 /* TODO: support group chats. I think the number in msg_text has | |
1552 * something to do with the 'gid' field. */ | |
1553 purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text); | |
1554 | |
1555 rc = TRUE; | |
1556 } else if (strstr(msg_text, "!!!Offline=")) { | |
1557 /* TODO: support group chats. This one might mean a user | |
1558 * went offline or exited the chat. */ | |
1559 purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text); | |
1560 | |
1561 rc = TRUE; | |
1562 } else if (msim_msg_get_integer(msg, "aid") != 0) { | |
1563 purple_debug_info("msim", "TODO: implement #4691, group chat from %d on %d: %s\n", | |
1564 msim_msg_get_integer(msg, "aid"), | |
1565 msim_msg_get_integer(msg, "f"), | |
1566 msg_text); | |
1567 | |
1568 rc = TRUE; | |
1569 } else { | |
1570 msim_unrecognized(session, msg, | |
1571 "got to msim_incoming_action but unrecognized value for 'msg'"); | |
1572 rc = FALSE; | |
1573 } | |
1574 | |
1575 g_free(msg_text); | |
1576 g_free(username); | |
1577 | |
1578 return rc; | |
1579 } | |
1580 | |
1581 /** | |
1582 * Process an incoming media (message background?) message. | |
1583 */ | |
1584 static gboolean | |
1585 msim_incoming_media(MsimSession *session, MsimMessage *msg) | |
1586 { | |
1587 gchar *username, *text; | |
1588 | |
1589 username = msim_msg_get_string(msg, "_username"); | |
1590 text = msim_msg_get_string(msg, "msg"); | |
1591 | |
1592 g_return_val_if_fail(username != NULL, FALSE); | |
1593 g_return_val_if_fail(text != NULL, FALSE); | |
1594 | |
1595 purple_debug_info("msim", "msim_incoming_media: from %s, got msg=%s\n", username, text); | |
1596 | |
1597 /* Media messages are sent when the user opens a window to someone. | |
1598 * Tell libpurple they started typing and stopped typing, to inform the Psychic | |
1599 * Mode plugin so it too can open a window to the user. */ | |
1600 serv_got_typing(session->gc, username, 0, PURPLE_TYPING); | |
1601 serv_got_typing_stopped(session->gc, username); | |
1602 | |
1603 g_free(username); | |
1604 | |
1605 return TRUE; | |
1606 } | |
1607 | |
1608 /** | |
1609 * Process an incoming "unofficial client" message. The plugin for | |
1610 * Miranda IM sends this message with the plugin information. | |
1611 */ | |
1612 static gboolean | |
1613 msim_incoming_unofficial_client(MsimSession *session, MsimMessage *msg) | |
1614 { | |
1615 MsimUser *user; | |
1616 gchar *username, *client_info; | |
1617 | |
1618 username = msim_msg_get_string(msg, "_username"); | |
1619 client_info = msim_msg_get_string(msg, "msg"); | |
1620 | |
1621 g_return_val_if_fail(username != NULL, FALSE); | |
1622 g_return_val_if_fail(client_info != NULL, FALSE); | |
1623 | |
1624 purple_debug_info("msim", "msim_incoming_unofficial_client: %s is using client %s\n", | |
1625 username, client_info); | |
1626 | |
1627 user = msim_find_user(session, username); | |
1628 | |
1629 g_return_val_if_fail(user != NULL, FALSE); | |
1630 | |
1631 if (user->client_info) { | |
1632 g_free(user->client_info); | |
1633 } | |
1634 user->client_info = client_info; | |
1635 | |
1636 g_free(username); | |
1637 /* Do not free client_info - the MsimUser now owns it. */ | |
1638 | |
1639 return TRUE; | |
1640 } | |
1641 | |
1642 /** | |
1643 * Handle an incoming buddy message. | |
1644 */ | |
643 static gboolean | 1645 static gboolean |
644 msim_incoming_bm(MsimSession *session, MsimMessage *msg) | 1646 msim_incoming_bm(MsimSession *session, MsimMessage *msg) |
645 { | 1647 { |
646 guint bm; | 1648 guint bm; |
647 | 1649 |
659 case MSIM_BM_MEDIA: | 1661 case MSIM_BM_MEDIA: |
660 return msim_incoming_media(session, msg); | 1662 return msim_incoming_media(session, msg); |
661 case MSIM_BM_UNOFFICIAL_CLIENT: | 1663 case MSIM_BM_UNOFFICIAL_CLIENT: |
662 return msim_incoming_unofficial_client(session, msg); | 1664 return msim_incoming_unofficial_client(session, msg); |
663 default: | 1665 default: |
664 /* Not really an IM, but show it for informational | 1666 /* Not really an IM, but show it for informational |
665 * purposes during development. */ | 1667 * purposes during development. */ |
666 return msim_incoming_im(session, msg); | 1668 return msim_incoming_im(session, msg); |
667 } | 1669 } |
668 } | 1670 } |
669 | 1671 |
670 /** | 1672 /** |
671 * Handle an incoming instant message. | 1673 * Process the initial server information from the server. |
672 * | 1674 */ |
673 * @param session The session | 1675 static gboolean |
674 * @param msg Message from the server, containing 'f' (userid from) and 'msg'. | 1676 msim_process_server_info(MsimSession *session, MsimMessage *msg) |
675 * Should also contain username in _username from preprocessing. | 1677 { |
1678 MsimMessage *body; | |
1679 | |
1680 body = msim_msg_get_dictionary(msg, "body"); | |
1681 g_return_val_if_fail(body != NULL, FALSE); | |
1682 | |
1683 /* Example body: | |
1684 AdUnitRefreshInterval=10. | |
1685 AlertPollInterval=360. | |
1686 AllowChatRoomEmoticonSharing=False. | |
1687 ChatRoomUserIDs=78744676;163733130;1300326231;123521495;142663391. | |
1688 CurClientVersion=673. | |
1689 EnableIMBrowse=True. | |
1690 EnableIMStuffAvatars=False. | |
1691 EnableIMStuffZaps=False. | |
1692 MaxAddAllFriends=100. | |
1693 MaxContacts=1000. | |
1694 MinClientVersion=594. | |
1695 MySpaceIM_ENGLISH=78744676. | |
1696 MySpaceNowTimer=720. | |
1697 PersistenceDataTimeout=900. | |
1698 UseWebChallenge=1. | |
1699 WebTicketGoHome=False | |
1700 | |
1701 Anything useful? TODO: use what is useful, and use it. | |
1702 */ | |
1703 purple_debug_info("msim_process_server_info", | |
1704 "maximum contacts: %d\n", | |
1705 msim_msg_get_integer(body, "MaxContacts")); | |
1706 | |
1707 session->server_info = body; | |
1708 /* session->server_info freed in msim_session_destroy */ | |
1709 | |
1710 return TRUE; | |
1711 } | |
1712 | |
1713 /** | |
1714 * Process a web challenge, used to login to the web site. | |
1715 */ | |
1716 static gboolean | |
1717 msim_web_challenge(MsimSession *session, MsimMessage *msg) | |
1718 { | |
1719 /* TODO: web challenge, store token. #2659. */ | |
1720 return FALSE; | |
1721 } | |
1722 | |
1723 /** | |
1724 * Process a persistance message reply from the server. | |
1725 * | |
1726 * @param session | |
1727 * @param msg Message reply from server. | |
676 * | 1728 * |
677 * @return TRUE if successful. | 1729 * @return TRUE if successful. |
678 */ | 1730 * |
679 static gboolean | 1731 * msim_lookup_user sets callback for here |
680 msim_incoming_im(MsimSession *session, MsimMessage *msg) | 1732 */ |
681 { | 1733 static gboolean |
682 gchar *username, *msg_msim_markup, *msg_purple_markup; | 1734 msim_process_reply(MsimSession *session, MsimMessage *msg) |
683 gchar *userid; | 1735 { |
684 time_t time_received; | 1736 MSIM_USER_LOOKUP_CB cb; |
685 PurpleConversation *conv; | 1737 gpointer data; |
1738 guint rid, cmd, dsn, lid; | |
686 | 1739 |
687 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | 1740 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); |
688 g_return_val_if_fail(msg != NULL, FALSE); | 1741 g_return_val_if_fail(msg != NULL, FALSE); |
689 | 1742 |
690 username = msim_msg_get_string(msg, "_username"); | 1743 msim_store_user_info(session, msg, NULL); |
691 /* I know this isn't really a string... but we need it to be one for | 1744 |
692 * purple_find_conversation_with_account(). */ | 1745 rid = msim_msg_get_integer(msg, "rid"); |
693 userid = msim_msg_get_string(msg, "f"); | 1746 cmd = msim_msg_get_integer(msg, "cmd"); |
694 g_return_val_if_fail(username != NULL, FALSE); | 1747 dsn = msim_msg_get_integer(msg, "dsn"); |
695 | 1748 lid = msim_msg_get_integer(msg, "lid"); |
696 purple_debug_info("msim_incoming_im", "UserID is %s", userid); | 1749 |
697 | 1750 /* Unsolicited messages */ |
698 if (msim_is_userid(username)) { | 1751 if (cmd == (MSIM_CMD_BIT_REPLY | MSIM_CMD_GET)) { |
699 purple_debug_info("msim", "Ignoring message from spambot (%s) on account %s\n", | 1752 if (dsn == MG_SERVER_INFO_DSN && lid == MG_SERVER_INFO_LID) { |
700 username, purple_account_get_username(session->account)); | 1753 return msim_process_server_info(session, msg); |
701 g_free(username); | 1754 } else if (dsn == MG_WEB_CHALLENGE_DSN && lid == MG_WEB_CHALLENGE_LID) { |
702 return FALSE; | 1755 return msim_web_challenge(session, msg); |
703 } | 1756 } |
704 | 1757 } |
705 /* See if a conversation with their UID already exists...*/ | 1758 |
706 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, userid, session->account); | 1759 /* If a callback is registered for this userid lookup, call it. */ |
707 if (conv) { | 1760 cb = g_hash_table_lookup(session->user_lookup_cb, GUINT_TO_POINTER(rid)); |
708 /* Since the conversation exists... We need to normalize it */ | 1761 data = g_hash_table_lookup(session->user_lookup_cb_data, GUINT_TO_POINTER(rid)); |
709 purple_conversation_set_name(conv, username); | 1762 |
710 } | 1763 if (cb) { |
711 | 1764 purple_debug_info("msim", "msim_process_reply: calling callback now\n"); |
712 msg_msim_markup = msim_msg_get_string(msg, "msg"); | 1765 /* Clone message, so that the callback 'cb' can use it (needs to free it also). */ |
713 g_return_val_if_fail(msg_msim_markup != NULL, FALSE); | 1766 cb(session, msg, data); |
714 | 1767 g_hash_table_remove(session->user_lookup_cb, GUINT_TO_POINTER(rid)); |
715 msg_purple_markup = msim_markup_to_html(session, msg_msim_markup); | 1768 g_hash_table_remove(session->user_lookup_cb_data, GUINT_TO_POINTER(rid)); |
716 g_free(msg_msim_markup); | 1769 } else { |
717 | 1770 purple_debug_info("msim", |
718 time_received = msim_msg_get_integer(msg, "date"); | 1771 "msim_process_reply: no callback for rid %d\n", rid); |
719 if (!time_received) { | 1772 } |
720 purple_debug_info("msim_incoming_im", "date in message not set.\n"); | |
721 time_received = time(NULL); | |
722 } | |
723 | |
724 serv_got_im(session->gc, username, msg_purple_markup, PURPLE_MESSAGE_RECV, time_received); | |
725 | |
726 g_free(username); | |
727 g_free(msg_purple_markup); | |
728 | 1773 |
729 return TRUE; | 1774 return TRUE; |
730 } | 1775 } |
731 | 1776 |
732 /** | 1777 /** |
733 * Process unrecognized information. | 1778 * Handle an error from the server. |
734 * | 1779 * |
735 * @param session | 1780 * @param session |
736 * @param msg An MsimMessage that was unrecognized, or NULL. | 1781 * @param msg The message. |
737 * @param note Information on what was unrecognized, or NULL. | 1782 * |
738 */ | 1783 * @return TRUE if successfully reported error. |
739 void | 1784 */ |
740 msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note) | 1785 static gboolean |
741 { | 1786 msim_error(MsimSession *session, MsimMessage *msg) |
742 /* TODO: Some more context, outwardly equivalent to a backtrace, | 1787 { |
743 * for helping figure out what this msg is for. What was going on? | 1788 gchar *errmsg, *full_errmsg; |
744 * But not too much information so that a user | 1789 guint err; |
745 * posting this dump reveals confidential information. | |
746 */ | |
747 | |
748 /* TODO: dump unknown msgs to file, so user can send them to me | |
749 * if they wish, to help add support for new messages (inspired | |
750 * by Alexandr Shutko, who maintains OSCAR protocol documentation). | |
751 * | |
752 * Filed enhancement ticket for libpurple as #4688. | |
753 */ | |
754 | |
755 purple_debug_info("msim", "Unrecognized data on account for %s\n", | |
756 (session && session->account && session->account->username) ? | |
757 session->account->username : "(NULL)"); | |
758 if (note) { | |
759 purple_debug_info("msim", "(Note: %s)\n", note); | |
760 } | |
761 | |
762 if (msg) { | |
763 msim_msg_dump("Unrecognized message dump: %s\n", msg); | |
764 } | |
765 } | |
766 | |
767 /** | |
768 * Handle an incoming action message. | |
769 * | |
770 * @param session | |
771 * @param msg | |
772 * | |
773 * @return TRUE if successful. | |
774 * | |
775 */ | |
776 static gboolean | |
777 msim_incoming_action(MsimSession *session, MsimMessage *msg) | |
778 { | |
779 gchar *msg_text, *username; | |
780 gboolean rc; | |
781 | 1790 |
782 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | 1791 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); |
783 g_return_val_if_fail(msg != NULL, FALSE); | 1792 g_return_val_if_fail(msg != NULL, FALSE); |
784 | 1793 |
785 msg_text = msim_msg_get_string(msg, "msg"); | 1794 err = msim_msg_get_integer(msg, "err"); |
786 g_return_val_if_fail(msg_text != NULL, FALSE); | 1795 errmsg = msim_msg_get_string(msg, "errmsg"); |
787 | 1796 |
788 username = msim_msg_get_string(msg, "_username"); | 1797 full_errmsg = g_strdup_printf(_("Protocol error, code %d: %s"), err, |
789 g_return_val_if_fail(username != NULL, FALSE); | 1798 errmsg ? errmsg : "no 'errmsg' given"); |
790 | 1799 |
791 purple_debug_info("msim", "msim_incoming_action: action <%s> from <%s>\n", | 1800 g_free(errmsg); |
792 msg_text, username); | 1801 |
793 | 1802 purple_debug_info("msim", "msim_error (sesskey=%d): %s\n", |
794 if (g_str_equal(msg_text, "%typing%")) { | 1803 session->sesskey, full_errmsg); |
795 serv_got_typing(session->gc, username, 0, PURPLE_TYPING); | 1804 |
796 rc = TRUE; | 1805 /* Destroy session if fatal. */ |
797 } else if (g_str_equal(msg_text, "%stoptyping%")) { | 1806 if (msim_msg_get(msg, "fatal")) { |
798 serv_got_typing_stopped(session->gc, username); | 1807 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; |
799 rc = TRUE; | 1808 purple_debug_info("msim", "fatal error, closing\n"); |
800 } else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_")) { | 1809 |
801 rc = msim_incoming_zap(session, msg); | 1810 switch (err) { |
802 } else if (strstr(msg_text, "!!!GroupCount=")) { | 1811 case MSIM_ERROR_INCORRECT_PASSWORD: /* Incorrect password */ |
803 /* TODO: support group chats. I think the number in msg_text has | 1812 reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; |
804 * something to do with the 'gid' field. */ | 1813 if (!purple_account_get_remember_password(session->account)) |
805 purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text); | 1814 purple_account_set_password(session->account, NULL); |
806 | 1815 #ifdef MSIM_MAX_PASSWORD_LENGTH |
807 rc = TRUE; | 1816 if (session->account->password && (strlen(session->account->password) > MSIM_MAX_PASSWORD_LENGTH)) { |
808 } else if (strstr(msg_text, "!!!Offline=")) { | 1817 gchar *suggestion; |
809 /* TODO: support group chats. This one might mean a user | 1818 |
810 * went offline or exited the chat. */ | 1819 suggestion = g_strdup_printf(_("%s Your password is " |
811 purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text); | 1820 "%d characters, greater than the " |
812 | 1821 "expected maximum length of %d for " |
813 rc = TRUE; | 1822 "MySpaceIM. Please shorten your " |
814 } else if (msim_msg_get_integer(msg, "aid") != 0) { | 1823 "password at http://profileedit.myspace.com/index.cfm?fuseaction=accountSettings.changePassword and try again."), |
815 purple_debug_info("msim", "TODO: implement #4691, group chat from %d on %d: %s\n", | 1824 full_errmsg, (int) |
816 msim_msg_get_integer(msg, "aid"), | 1825 strlen(session->account->password), |
817 msim_msg_get_integer(msg, "f"), | 1826 MSIM_MAX_PASSWORD_LENGTH); |
818 msg_text); | 1827 |
819 | 1828 /* Replace full_errmsg. */ |
820 rc = TRUE; | 1829 g_free(full_errmsg); |
1830 full_errmsg = suggestion; | |
1831 } | |
1832 #endif | |
1833 break; | |
1834 case MSIM_ERROR_LOGGED_IN_ELSEWHERE: /* Logged in elsewhere */ | |
1835 reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE; | |
1836 if (!purple_account_get_remember_password(session->account)) | |
1837 purple_account_set_password(session->account, NULL); | |
1838 break; | |
1839 } | |
1840 purple_connection_error_reason (session->gc, reason, full_errmsg); | |
821 } else { | 1841 } else { |
822 msim_unrecognized(session, msg, | 1842 purple_notify_error(session->account, _("MySpaceIM Error"), full_errmsg, NULL); |
823 "got to msim_incoming_action but unrecognized value for 'msg'"); | 1843 } |
824 rc = FALSE; | 1844 |
825 } | 1845 g_free(full_errmsg); |
826 | |
827 g_free(msg_text); | |
828 g_free(username); | |
829 | |
830 return rc; | |
831 } | |
832 | |
833 /* Process an incoming media (message background?) message. */ | |
834 static gboolean | |
835 msim_incoming_media(MsimSession *session, MsimMessage *msg) | |
836 { | |
837 gchar *username, *text; | |
838 | |
839 username = msim_msg_get_string(msg, "_username"); | |
840 text = msim_msg_get_string(msg, "msg"); | |
841 | |
842 g_return_val_if_fail(username != NULL, FALSE); | |
843 g_return_val_if_fail(text != NULL, FALSE); | |
844 | |
845 purple_debug_info("msim", "msim_incoming_media: from %s, got msg=%s\n", username, text); | |
846 | |
847 /* Media messages are sent when the user opens a window to someone. | |
848 * Tell libpurple they started typing and stopped typing, to inform the Psychic | |
849 * Mode plugin so it too can open a window to the user. */ | |
850 serv_got_typing(session->gc, username, 0, PURPLE_TYPING); | |
851 serv_got_typing_stopped(session->gc, username); | |
852 | |
853 g_free(username); | |
854 | 1846 |
855 return TRUE; | 1847 return TRUE; |
856 } | 1848 } |
857 | 1849 |
858 /* Process an incoming "unofficial client" message. The plugin for | 1850 /** |
859 * Miranda IM sends this message with the plugin information. */ | 1851 * Process a message. |
860 static gboolean | |
861 msim_incoming_unofficial_client(MsimSession *session, MsimMessage *msg) | |
862 { | |
863 MsimUser *user; | |
864 gchar *username, *client_info; | |
865 | |
866 username = msim_msg_get_string(msg, "_username"); | |
867 client_info = msim_msg_get_string(msg, "msg"); | |
868 | |
869 g_return_val_if_fail(username != NULL, FALSE); | |
870 g_return_val_if_fail(client_info != NULL, FALSE); | |
871 | |
872 purple_debug_info("msim", "msim_incoming_unofficial_client: %s is using client %s\n", | |
873 username, client_info); | |
874 | |
875 user = msim_find_user(session, username); | |
876 | |
877 g_return_val_if_fail(user != NULL, FALSE); | |
878 | |
879 if (user->client_info) { | |
880 g_free(user->client_info); | |
881 } | |
882 user->client_info = client_info; | |
883 | |
884 g_free(username); | |
885 /* Do not free client_info - the MsimUser now owns it. */ | |
886 | |
887 return TRUE; | |
888 } | |
889 | |
890 | |
891 #ifdef MSIM_SEND_CLIENT_VERSION | |
892 /** Send our client version to another unofficial client that understands it. */ | |
893 static gboolean | |
894 msim_send_unofficial_client(MsimSession *session, gchar *username) | |
895 { | |
896 gchar *our_info; | |
897 gboolean ret; | |
898 | |
899 our_info = g_strdup_printf("Libpurple %d.%d.%d - msimprpl %s", | |
900 PURPLE_MAJOR_VERSION, | |
901 PURPLE_MINOR_VERSION, | |
902 PURPLE_MICRO_VERSION, | |
903 MSIM_PRPL_VERSION_STRING); | |
904 | |
905 ret = msim_send_bm(session, username, our_info, MSIM_BM_UNOFFICIAL_CLIENT); | |
906 | |
907 return ret; | |
908 } | |
909 #endif | |
910 | |
911 /** | |
912 * Handle when our user starts or stops typing to another user. | |
913 * | |
914 * @param gc | |
915 * @param name The buddy name to which our user is typing to | |
916 * @param state PURPLE_TYPING, PURPLE_TYPED, PURPLE_NOT_TYPING | |
917 * | |
918 * @return 0 | |
919 */ | |
920 unsigned int | |
921 msim_send_typing(PurpleConnection *gc, const gchar *name, | |
922 PurpleTypingState state) | |
923 { | |
924 const gchar *typing_str; | |
925 MsimSession *session; | |
926 | |
927 g_return_val_if_fail(gc != NULL, 0); | |
928 g_return_val_if_fail(name != NULL, 0); | |
929 | |
930 session = (MsimSession *)gc->proto_data; | |
931 | |
932 g_return_val_if_fail(MSIM_SESSION_VALID(session), 0); | |
933 | |
934 switch (state) { | |
935 case PURPLE_TYPING: | |
936 typing_str = "%typing%"; | |
937 break; | |
938 | |
939 case PURPLE_TYPED: | |
940 case PURPLE_NOT_TYPING: | |
941 default: | |
942 typing_str = "%stoptyping%"; | |
943 break; | |
944 } | |
945 | |
946 purple_debug_info("msim", "msim_send_typing(%s): %d (%s)\n", name, state, typing_str); | |
947 msim_send_bm(session, name, typing_str, MSIM_BM_ACTION); | |
948 return 0; | |
949 } | |
950 | |
951 | |
952 | |
953 /** Callback for msim_get_info(), for when user info is received. */ | |
954 static void | |
955 msim_get_info_cb(MsimSession *session, MsimMessage *user_info_msg, | |
956 gpointer data) | |
957 { | |
958 MsimMessage *msg; | |
959 gchar *username; | |
960 PurpleNotifyUserInfo *user_info; | |
961 MsimUser *user; | |
962 | |
963 g_return_if_fail(MSIM_SESSION_VALID(session)); | |
964 | |
965 /* Get user{name,id} from msim_get_info, passed as an MsimMessage for | |
966 orthogonality. */ | |
967 msg = (MsimMessage *)data; | |
968 g_return_if_fail(msg != NULL); | |
969 | |
970 username = msim_msg_get_string(msg, "user"); | |
971 if (!username) { | |
972 purple_debug_info("msim", "msim_get_info_cb: no 'user' in msg\n"); | |
973 return; | |
974 } | |
975 | |
976 msim_msg_free(msg); | |
977 purple_debug_info("msim", "msim_get_info_cb: got for user: %s\n", username); | |
978 | |
979 user = msim_find_user(session, username); | |
980 | |
981 if (!user) { | |
982 /* User isn't on blist, create a temporary user to store info. */ | |
983 user = g_new0(MsimUser, 1); | |
984 user->temporary_user = TRUE; | |
985 } | |
986 | |
987 /* Update user structure with new information */ | |
988 msim_store_user_info(session, user_info_msg, user); | |
989 | |
990 user_info = purple_notify_user_info_new(); | |
991 | |
992 /* Append data from MsimUser to PurpleNotifyUserInfo for display, full */ | |
993 msim_append_user_info(session, user_info, user, TRUE); | |
994 | |
995 purple_notify_userinfo(session->gc, username, user_info, NULL, NULL); | |
996 purple_debug_info("msim", "msim_get_info_cb: username=%s\n", username); | |
997 | |
998 purple_notify_user_info_destroy(user_info); | |
999 | |
1000 if (user->temporary_user) { | |
1001 g_free(user->client_info); | |
1002 g_free(user->gender); | |
1003 g_free(user->location); | |
1004 g_free(user->headline); | |
1005 g_free(user->display_name); | |
1006 g_free(user->username); | |
1007 g_free(user->image_url); | |
1008 g_free(user); | |
1009 } | |
1010 g_free(username); | |
1011 } | |
1012 | |
1013 /** Retrieve a user's profile. | |
1014 * @param username Username, user ID, or email address to lookup. | |
1015 */ | |
1016 void | |
1017 msim_get_info(PurpleConnection *gc, const gchar *username) | |
1018 { | |
1019 MsimSession *session; | |
1020 MsimUser *user; | |
1021 gchar *user_to_lookup; | |
1022 MsimMessage *user_msg; | |
1023 | |
1024 g_return_if_fail(gc != NULL); | |
1025 g_return_if_fail(username != NULL); | |
1026 | |
1027 session = (MsimSession *)gc->proto_data; | |
1028 | |
1029 g_return_if_fail(MSIM_SESSION_VALID(session)); | |
1030 | |
1031 /* Obtain uid of buddy. */ | |
1032 user = msim_find_user(session, username); | |
1033 | |
1034 /* If is on buddy list, lookup by uid since it is faster. */ | |
1035 if (user && user->id) { | |
1036 user_to_lookup = g_strdup_printf("%d", user->id); | |
1037 } else { | |
1038 /* Looking up buddy not on blist. Lookup by whatever user entered. */ | |
1039 user_to_lookup = g_strdup(username); | |
1040 } | |
1041 | |
1042 /* Pass the username to msim_get_info_cb(), because since we lookup | |
1043 * by userid, the userinfo message will only contain the uid (not | |
1044 * the username) but it would be useful to display the username too. | |
1045 */ | |
1046 user_msg = msim_msg_new( | |
1047 "user", MSIM_TYPE_STRING, g_strdup(username), | |
1048 NULL); | |
1049 purple_debug_info("msim", "msim_get_info, setting up lookup, user=%s\n", username); | |
1050 | |
1051 msim_lookup_user(session, user_to_lookup, msim_get_info_cb, user_msg); | |
1052 | |
1053 g_free(user_to_lookup); | |
1054 } | |
1055 | |
1056 /** Set your status - callback for when user manually sets it. */ | |
1057 void | |
1058 msim_set_status(PurpleAccount *account, PurpleStatus *status) | |
1059 { | |
1060 PurpleStatusType *type; | |
1061 PurplePresence *pres; | |
1062 MsimSession *session; | |
1063 guint status_code; | |
1064 const gchar *message; | |
1065 gchar *stripped; | |
1066 gchar *unrecognized_msg; | |
1067 | |
1068 session = (MsimSession *)account->gc->proto_data; | |
1069 | |
1070 g_return_if_fail(MSIM_SESSION_VALID(session)); | |
1071 | |
1072 type = purple_status_get_type(status); | |
1073 pres = purple_status_get_presence(status); | |
1074 | |
1075 switch (purple_status_type_get_primitive(type)) { | |
1076 case PURPLE_STATUS_AVAILABLE: | |
1077 purple_debug_info("msim", "msim_set_status: available (%d->%d)\n", PURPLE_STATUS_AVAILABLE, | |
1078 MSIM_STATUS_CODE_ONLINE); | |
1079 status_code = MSIM_STATUS_CODE_ONLINE; | |
1080 break; | |
1081 | |
1082 case PURPLE_STATUS_INVISIBLE: | |
1083 purple_debug_info("msim", "msim_set_status: invisible (%d->%d)\n", PURPLE_STATUS_INVISIBLE, | |
1084 MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN); | |
1085 status_code = MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN; | |
1086 break; | |
1087 | |
1088 case PURPLE_STATUS_AWAY: | |
1089 purple_debug_info("msim", "msim_set_status: away (%d->%d)\n", PURPLE_STATUS_AWAY, | |
1090 MSIM_STATUS_CODE_AWAY); | |
1091 status_code = MSIM_STATUS_CODE_AWAY; | |
1092 break; | |
1093 | |
1094 default: | |
1095 purple_debug_info("msim", "msim_set_status: unknown " | |
1096 "status interpreting as online"); | |
1097 status_code = MSIM_STATUS_CODE_ONLINE; | |
1098 | |
1099 unrecognized_msg = g_strdup_printf("msim_set_status, unrecognized status type: %d\n", | |
1100 purple_status_type_get_primitive(type)); | |
1101 msim_unrecognized(session, NULL, unrecognized_msg); | |
1102 g_free(unrecognized_msg); | |
1103 | |
1104 break; | |
1105 } | |
1106 | |
1107 message = purple_status_get_attr_string(status, "message"); | |
1108 | |
1109 /* Status strings are plain text. */ | |
1110 if (message != NULL) | |
1111 stripped = purple_markup_strip_html(message); | |
1112 else | |
1113 stripped = g_strdup(""); | |
1114 | |
1115 msim_set_status_code(session, status_code, stripped); | |
1116 | |
1117 /* If we should be idle, set that status. Time is irrelevant here. */ | |
1118 if (purple_presence_is_idle(pres) && status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN) | |
1119 msim_set_idle(account->gc, 1); | |
1120 | |
1121 } | |
1122 | |
1123 /** Go idle. */ | |
1124 void | |
1125 msim_set_idle(PurpleConnection *gc, int time) | |
1126 { | |
1127 MsimSession *session; | |
1128 PurpleStatus *status; | |
1129 | |
1130 g_return_if_fail(gc != NULL); | |
1131 | |
1132 session = (MsimSession *)gc->proto_data; | |
1133 | |
1134 g_return_if_fail(MSIM_SESSION_VALID(session)); | |
1135 | |
1136 status = purple_account_get_active_status(session->account); | |
1137 | |
1138 if (time == 0) { | |
1139 /* Going back from idle. In msim, idle is mutually exclusive | |
1140 * from the other states (you can only be away or idle, but not | |
1141 * both, for example), so by going non-idle I go back to what | |
1142 * libpurple says I should be. | |
1143 */ | |
1144 msim_set_status(session->account, status); | |
1145 } else { | |
1146 const gchar *message; | |
1147 gchar *stripped; | |
1148 | |
1149 /* Set the idle message to the status message from the real | |
1150 * current status. | |
1151 */ | |
1152 message = purple_status_get_attr_string(status, "message"); | |
1153 if (message != NULL) | |
1154 stripped = purple_markup_strip_html(message); | |
1155 else | |
1156 stripped = g_strdup(""); | |
1157 | |
1158 /* msim doesn't support idle time, so just go idle */ | |
1159 msim_set_status_code(session, MSIM_STATUS_CODE_IDLE, stripped); | |
1160 } | |
1161 } | |
1162 | |
1163 /** Set status using an MSIM_STATUS_CODE_* value. | |
1164 * @param status_code An MSIM_STATUS_CODE_* value. | |
1165 * @param statstring Status string, must be a dynamic string (will be freed by msim_send). | |
1166 */ | |
1167 static void | |
1168 msim_set_status_code(MsimSession *session, guint status_code, gchar *statstring) | |
1169 { | |
1170 g_return_if_fail(MSIM_SESSION_VALID(session)); | |
1171 g_return_if_fail(statstring != NULL); | |
1172 | |
1173 purple_debug_info("msim", "msim_set_status_code: going to set status to code=%d,str=%s\n", | |
1174 status_code, statstring); | |
1175 | |
1176 if (!msim_send(session, | |
1177 "status", MSIM_TYPE_INTEGER, status_code, | |
1178 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
1179 "statstring", MSIM_TYPE_STRING, statstring, | |
1180 "locstring", MSIM_TYPE_STRING, g_strdup(""), | |
1181 NULL)) | |
1182 { | |
1183 purple_debug_info("msim", "msim_set_status: failed to set status\n"); | |
1184 } | |
1185 | |
1186 } | |
1187 | |
1188 /** After a uid is resolved to username, tag it with the username and submit for processing. | |
1189 * | |
1190 * @param session | |
1191 * @param userinfo Response messsage to resolving request. | |
1192 * @param data MsimMessage *, the message to attach information to. | |
1193 */ | |
1194 static void | |
1195 msim_incoming_resolved(MsimSession *session, MsimMessage *userinfo, | |
1196 gpointer data) | |
1197 { | |
1198 gchar *username; | |
1199 MsimMessage *msg, *body; | |
1200 | |
1201 g_return_if_fail(MSIM_SESSION_VALID(session)); | |
1202 g_return_if_fail(userinfo != NULL); | |
1203 | |
1204 body = msim_msg_get_dictionary(userinfo, "body"); | |
1205 g_return_if_fail(body != NULL); | |
1206 | |
1207 username = msim_msg_get_string(body, "UserName"); | |
1208 g_return_if_fail(username != NULL); | |
1209 /* Note: username will be owned by 'msg' below. */ | |
1210 | |
1211 msg = (MsimMessage *)data; | |
1212 g_return_if_fail(msg != NULL); | |
1213 | |
1214 /* TODO: more elegant solution than below. attach whole message? */ | |
1215 /* Special elements name beginning with '_', we'll use internally within the | |
1216 * program (did not come directly from the wire). */ | |
1217 msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, username); /* This makes 'msg' the owner of 'username' */ | |
1218 | |
1219 /* TODO: attach more useful information, like ImageURL */ | |
1220 | |
1221 msim_process(session, msg); | |
1222 | |
1223 /* TODO: Free copy cloned from msim_preprocess_incoming(). */ | |
1224 /* msim_msg_free(msg); */ | |
1225 msim_msg_free(body); | |
1226 } | |
1227 | |
1228 /* Lookup a username by userid, from buddy list. | |
1229 * | |
1230 * @param wanted_uid | |
1231 * | |
1232 * @return Username of wanted_uid, if on blist, or NULL. | |
1233 * This is a static string, so don't free it. Copy it if needed. | |
1234 * | |
1235 */ | |
1236 static const gchar * | |
1237 msim_uid2username_from_blist(PurpleAccount *account, guint wanted_uid) | |
1238 { | |
1239 GSList *buddies, *cur; | |
1240 const gchar *ret; | |
1241 | |
1242 buddies = purple_find_buddies(account, NULL); | |
1243 | |
1244 if (!buddies) | |
1245 { | |
1246 purple_debug_info("msim", "msim_uid2username_from_blist: no buddies?\n"); | |
1247 return NULL; | |
1248 } | |
1249 | |
1250 ret = NULL; | |
1251 | |
1252 for (cur = buddies; cur != NULL; cur = g_slist_next(cur)) | |
1253 { | |
1254 PurpleBuddy *buddy; | |
1255 guint uid; | |
1256 const gchar *name; | |
1257 | |
1258 /* See finch/gnthistory.c */ | |
1259 buddy = cur->data; | |
1260 | |
1261 uid = purple_blist_node_get_int(&buddy->node, "UserID"); | |
1262 name = purple_buddy_get_name(buddy); | |
1263 | |
1264 if (uid == wanted_uid) | |
1265 { | |
1266 ret = name; | |
1267 break; | |
1268 } | |
1269 } | |
1270 | |
1271 g_slist_free(buddies); | |
1272 return ret; | |
1273 } | |
1274 | |
1275 /** Preprocess incoming messages, resolving as needed, calling msim_process() when ready to process. | |
1276 * | |
1277 * @param session | |
1278 * @param msg MsimMessage *, freed by caller. | |
1279 */ | |
1280 static gboolean | |
1281 msim_preprocess_incoming(MsimSession *session, MsimMessage *msg) | |
1282 { | |
1283 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
1284 g_return_val_if_fail(msg != NULL, FALSE); | |
1285 | |
1286 if (msim_msg_get(msg, "bm") && msim_msg_get(msg, "f")) { | |
1287 guint uid; | |
1288 const gchar *username; | |
1289 | |
1290 /* 'f' = userid message is from, in buddy messages */ | |
1291 uid = msim_msg_get_integer(msg, "f"); | |
1292 | |
1293 username = msim_uid2username_from_blist(session->account, uid); | |
1294 | |
1295 if (username) { | |
1296 /* Know username already, use it. */ | |
1297 purple_debug_info("msim", "msim_preprocess_incoming: tagging with _username=%s\n", | |
1298 username); | |
1299 msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username)); | |
1300 return msim_process(session, msg); | |
1301 | |
1302 } else { | |
1303 gchar *from; | |
1304 | |
1305 /* Send lookup request. */ | |
1306 /* XXX: where is msim_msg_get_string() freed? make _strdup and _nonstrdup. */ | |
1307 purple_debug_info("msim", "msim_incoming: sending lookup, setting up callback\n"); | |
1308 from = msim_msg_get_string(msg, "f"); | |
1309 msim_lookup_user(session, from, msim_incoming_resolved, msim_msg_clone(msg)); | |
1310 g_free(from); | |
1311 | |
1312 /* indeterminate */ | |
1313 return TRUE; | |
1314 } | |
1315 } else { | |
1316 /* Nothing to resolve - send directly to processing. */ | |
1317 return msim_process(session, msg); | |
1318 } | |
1319 } | |
1320 | |
1321 #ifdef MSIM_USE_KEEPALIVE | |
1322 /** Check if the connection is still alive, based on last communication. */ | |
1323 static gboolean | |
1324 msim_check_alive(gpointer data) | |
1325 { | |
1326 MsimSession *session; | |
1327 time_t delta; | |
1328 | |
1329 session = (MsimSession *)data; | |
1330 | |
1331 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
1332 | |
1333 delta = time(NULL) - session->last_comm; | |
1334 | |
1335 /* purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta); */ | |
1336 if (delta >= MSIM_KEEPALIVE_INTERVAL) { | |
1337 purple_debug_info("msim", | |
1338 "msim_check_alive: %zu > interval of %d, presumed dead\n", | |
1339 delta, MSIM_KEEPALIVE_INTERVAL); | |
1340 purple_connection_error_reason(session->gc, | |
1341 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, | |
1342 _("Lost connection with server")); | |
1343 | |
1344 return FALSE; | |
1345 } | |
1346 | |
1347 return TRUE; | |
1348 } | |
1349 #endif | |
1350 | |
1351 /** Handle mail reply checks. */ | |
1352 static void | |
1353 msim_check_inbox_cb(MsimSession *session, MsimMessage *reply, gpointer data) | |
1354 { | |
1355 MsimMessage *body; | |
1356 guint old_inbox_status; | |
1357 guint i, n; | |
1358 const gchar *froms[5], *tos[5], *urls[5], *subjects[5]; | |
1359 | |
1360 /* Information for each new inbox message type. */ | |
1361 static struct | |
1362 { | |
1363 const gchar *key; | |
1364 guint bit; | |
1365 const gchar *url; | |
1366 const gchar *text; | |
1367 } message_types[] = { | |
1368 { "Mail", MSIM_INBOX_MAIL, "http://messaging.myspace.com/index.cfm?fuseaction=mail.inbox", NULL }, | |
1369 { "BlogComment", MSIM_INBOX_BLOG_COMMENT, "http://blog.myspace.com/index.cfm?fuseaction=blog", NULL }, | |
1370 { "ProfileComment", MSIM_INBOX_PROFILE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL }, | |
1371 { "FriendRequest", MSIM_INBOX_FRIEND_REQUEST, "http://messaging.myspace.com/index.cfm?fuseaction=mail.friendRequests", NULL }, | |
1372 { "PictureComment", MSIM_INBOX_PICTURE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL } | |
1373 }; | |
1374 | |
1375 /* Can't write _()'d strings in array initializers. Workaround. */ | |
1376 message_types[0].text = _("New mail messages"); | |
1377 message_types[1].text = _("New blog comments"); | |
1378 message_types[2].text = _("New profile comments"); | |
1379 message_types[3].text = _("New friend requests!"); | |
1380 message_types[4].text = _("New picture comments"); | |
1381 | |
1382 g_return_if_fail(reply != NULL); | |
1383 | |
1384 msim_msg_dump("msim_check_inbox_cb: reply=%s\n", reply); | |
1385 | |
1386 body = msim_msg_get_dictionary(reply, "body"); | |
1387 | |
1388 if (body == NULL) | |
1389 return; | |
1390 | |
1391 old_inbox_status = session->inbox_status; | |
1392 | |
1393 n = 0; | |
1394 | |
1395 for (i = 0; i < sizeof(message_types) / sizeof(message_types[0]); ++i) { | |
1396 const gchar *key; | |
1397 guint bit; | |
1398 | |
1399 key = message_types[i].key; | |
1400 bit = message_types[i].bit; | |
1401 | |
1402 if (msim_msg_get(body, key)) { | |
1403 /* Notify only on when _changes_ from no mail -> has mail | |
1404 * (edge triggered) */ | |
1405 if (!(session->inbox_status & bit)) { | |
1406 purple_debug_info("msim", "msim_check_inbox_cb: got %s, at %d\n", | |
1407 key ? key : "(NULL)", n); | |
1408 | |
1409 subjects[n] = message_types[i].text; | |
1410 froms[n] = _("MySpace"); | |
1411 tos[n] = session->username; | |
1412 /* TODO: append token, web challenge, so automatically logs in. | |
1413 * Would also need to free strings because they won't be static | |
1414 */ | |
1415 urls[n] = message_types[i].url; | |
1416 | |
1417 ++n; | |
1418 } else { | |
1419 purple_debug_info("msim", | |
1420 "msim_check_inbox_cb: already notified of %s\n", | |
1421 key ? key : "(NULL)"); | |
1422 } | |
1423 | |
1424 session->inbox_status |= bit; | |
1425 } | |
1426 } | |
1427 | |
1428 if (n) { | |
1429 purple_debug_info("msim", | |
1430 "msim_check_inbox_cb: notifying of %d\n", n); | |
1431 | |
1432 /* TODO: free strings with callback _if_ change to dynamic (w/ token) */ | |
1433 purple_notify_emails(session->gc, /* handle */ | |
1434 n, /* count */ | |
1435 TRUE, /* detailed */ | |
1436 subjects, froms, tos, urls, | |
1437 NULL, /* PurpleNotifyCloseCallback cb */ | |
1438 NULL); /* gpointer user_data */ | |
1439 | |
1440 } | |
1441 | |
1442 msim_msg_free(body); | |
1443 } | |
1444 | |
1445 /* Send request to check if there is new mail. */ | |
1446 static gboolean | |
1447 msim_check_inbox(gpointer data) | |
1448 { | |
1449 MsimSession *session; | |
1450 | |
1451 session = (MsimSession *)data; | |
1452 | |
1453 if (!MSIM_SESSION_VALID(session)) { | |
1454 purple_debug_info("msim", "msim_check_inbox: session invalid, stopping the mail check.\n"); | |
1455 return FALSE; | |
1456 } | |
1457 | |
1458 purple_debug_info("msim", "msim_check_inbox: checking mail\n"); | |
1459 g_return_val_if_fail(msim_send(session, | |
1460 "persist", MSIM_TYPE_INTEGER, 1, | |
1461 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
1462 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET, | |
1463 "dsn", MSIM_TYPE_INTEGER, MG_CHECK_MAIL_DSN, | |
1464 "lid", MSIM_TYPE_INTEGER, MG_CHECK_MAIL_LID, | |
1465 "uid", MSIM_TYPE_INTEGER, session->userid, | |
1466 "rid", MSIM_TYPE_INTEGER, | |
1467 msim_new_reply_callback(session, msim_check_inbox_cb, NULL), | |
1468 "body", MSIM_TYPE_STRING, g_strdup(""), | |
1469 NULL), TRUE); | |
1470 | |
1471 /* Always return true, so that we keep checking for mail. */ | |
1472 return TRUE; | |
1473 } | |
1474 | |
1475 #ifdef MSIM_CHECK_NEWER_VERSION | |
1476 /** Callback for when a currentversion.txt has been downloaded. */ | |
1477 static void | |
1478 msim_check_newer_version_cb(PurpleUtilFetchUrlData *url_data, | |
1479 gpointer user_data, | |
1480 const gchar *url_text, | |
1481 gsize len, | |
1482 const gchar *error_message) | |
1483 { | |
1484 GKeyFile *keyfile; | |
1485 GError *error; | |
1486 GString *data; | |
1487 gchar *newest_filever; | |
1488 | |
1489 if (!url_text) { | |
1490 purple_debug_info("msim_check_newer_version_cb", | |
1491 "got error: %s\n", error_message); | |
1492 return; | |
1493 } | |
1494 | |
1495 purple_debug_info("msim_check_newer_version_cb", | |
1496 "url_text=%s\n", url_text ? url_text : "(NULL)"); | |
1497 | |
1498 /* Prepend [group] so that GKeyFile can parse it (requires a group). */ | |
1499 data = g_string_new(url_text); | |
1500 purple_debug_info("msim", "data=%s\n", data->str | |
1501 ? data->str : "(NULL)"); | |
1502 data = g_string_prepend(data, "[group]\n"); | |
1503 | |
1504 purple_debug_info("msim", "data=%s\n", data->str | |
1505 ? data->str : "(NULL)"); | |
1506 | |
1507 /* url_text is variable=data\n...†*/ | |
1508 | |
1509 /* Check FILEVER, 1.0.716.0. 716 is build, MSIM_CLIENT_VERSION */ | |
1510 /* New (english) version can be downloaded from SETUPURL+SETUPFILE */ | |
1511 | |
1512 error = NULL; | |
1513 keyfile = g_key_file_new(); | |
1514 | |
1515 /* Default list seperator is ;, but currentversion.txt doesn't have | |
1516 * these, so set to an unused character to avoid parsing problems. */ | |
1517 g_key_file_set_list_separator(keyfile, '\0'); | |
1518 | |
1519 g_key_file_load_from_data(keyfile, data->str, data->len, | |
1520 G_KEY_FILE_NONE, &error); | |
1521 g_string_free(data, TRUE); | |
1522 | |
1523 if (error != NULL) { | |
1524 purple_debug_info("msim_check_newer_version_cb", | |
1525 "couldn't parse, error: %d %d %s\n", | |
1526 error->domain, error->code, error->message); | |
1527 g_error_free(error); | |
1528 return; | |
1529 } | |
1530 | |
1531 gchar **ks; | |
1532 guint n; | |
1533 ks = g_key_file_get_keys(keyfile, "group", &n, NULL); | |
1534 purple_debug_info("msim", "n=%d\n", n); | |
1535 guint i; | |
1536 for (i = 0; ks[i] != NULL; ++i) | |
1537 { | |
1538 purple_debug_info("msim", "%d=%s\n", i, ks[i]); | |
1539 } | |
1540 | |
1541 newest_filever = g_key_file_get_string(keyfile, "group", | |
1542 "FILEVER", &error); | |
1543 | |
1544 purple_debug_info("msim_check_newer_version_cb", | |
1545 "newest filever: %s\n", newest_filever ? | |
1546 newest_filever : "(NULL)"); | |
1547 if (error != NULL) { | |
1548 purple_debug_info("msim_check_newer_version_cb", | |
1549 "error: %d %d %s\n", | |
1550 error->domain, error->code, error->message); | |
1551 g_error_free(error); | |
1552 } | |
1553 | |
1554 g_key_file_free(keyfile); | |
1555 | |
1556 exit(0); | |
1557 } | |
1558 #endif | |
1559 | |
1560 /** Called when the session key arrives to check whether the user | |
1561 * has a username, and set one if desired. */ | |
1562 static gboolean | |
1563 msim_is_username_set(MsimSession *session, MsimMessage *msg) | |
1564 { | |
1565 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
1566 g_return_val_if_fail(msg != NULL, FALSE); | |
1567 g_return_val_if_fail(session->gc != NULL, FALSE); | |
1568 | |
1569 session->sesskey = msim_msg_get_integer(msg, "sesskey"); | |
1570 purple_debug_info("msim", "SESSKEY=<%d>\n", session->sesskey); | |
1571 | |
1572 /* What is proof? Used to be uid, but now is 52 base64'd bytes... */ | |
1573 | |
1574 /* Comes with: proof,profileid,userid,uniquenick -- all same values | |
1575 * some of the time, but can vary. This is our own user ID. */ | |
1576 session->userid = msim_msg_get_integer(msg, "userid"); | |
1577 | |
1578 /* Save uid to account so this account can be looked up by uid. */ | |
1579 purple_account_set_int(session->account, "uid", session->userid); | |
1580 | |
1581 /* Not sure what profileid is used for. */ | |
1582 if (msim_msg_get_integer(msg, "profileid") != session->userid) { | |
1583 msim_unrecognized(session, msg, | |
1584 "Profile ID didn't match user ID, don't know why"); | |
1585 } | |
1586 | |
1587 /* We now know are our own username, only after we're logged in.. | |
1588 * which is weird, but happens because you login with your email | |
1589 * address and not username. Will be freed in msim_session_destroy(). */ | |
1590 session->username = msim_msg_get_string(msg, "uniquenick"); | |
1591 | |
1592 /* If user lacks a username, help them get one. */ | |
1593 if (msim_msg_get_integer(msg, "uniquenick") == session->userid) { | |
1594 purple_debug_info("msim_is_username_set", "no username is set\n"); | |
1595 purple_request_yes_no(session->gc, | |
1596 _("MySpaceIM - No Username Set"), | |
1597 _("You appear to have no MySpace username."), | |
1598 _("Would you like to set one now? (Note: THIS CANNOT BE CHANGED!)"), | |
1599 0, | |
1600 session->account, | |
1601 NULL, | |
1602 NULL, | |
1603 session->gc, | |
1604 G_CALLBACK(msim_set_username_cb), | |
1605 G_CALLBACK(msim_do_not_set_username_cb)); | |
1606 purple_debug_info("msim_is_username_set","'username not set' alert prompted\n"); | |
1607 return FALSE; | |
1608 } | |
1609 return TRUE; | |
1610 } | |
1611 | |
1612 /** Called after username is set, if necessary and we're open for business. */ | |
1613 gboolean msim_we_are_logged_on(MsimSession *session) | |
1614 { | |
1615 MsimMessage *body; | |
1616 | |
1617 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
1618 | |
1619 /* The session is now set up, ready to be connected. This emits the | |
1620 * signedOn signal, so clients can now do anything with msimprpl, and | |
1621 * we're ready for it (session key, userid, username all setup). */ | |
1622 purple_connection_update_progress(session->gc, _("Connected"), 3, 4); | |
1623 purple_connection_set_state(session->gc, PURPLE_CONNECTED); | |
1624 | |
1625 /* Set display name to username (otherwise will show email address) */ | |
1626 purple_connection_set_display_name(session->gc, session->username); | |
1627 | |
1628 body = msim_msg_new( | |
1629 "UserID", MSIM_TYPE_INTEGER, session->userid, | |
1630 NULL); | |
1631 | |
1632 /* Request IM info about ourself. */ | |
1633 msim_send(session, | |
1634 "persist", MSIM_TYPE_STRING, g_strdup("persist"), | |
1635 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
1636 "dsn", MSIM_TYPE_INTEGER, MG_OWN_MYSPACE_INFO_DSN, | |
1637 "uid", MSIM_TYPE_INTEGER, session->userid, | |
1638 "lid", MSIM_TYPE_INTEGER, MG_OWN_MYSPACE_INFO_LID, | |
1639 "rid", MSIM_TYPE_INTEGER, session->next_rid++, | |
1640 "body", MSIM_TYPE_DICTIONARY, body, | |
1641 NULL); | |
1642 | |
1643 /* Request MySpace info about ourself. */ | |
1644 msim_send(session, | |
1645 "persist", MSIM_TYPE_STRING, g_strdup("persist"), | |
1646 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
1647 "dsn", MSIM_TYPE_INTEGER, MG_OWN_IM_INFO_DSN, | |
1648 "uid", MSIM_TYPE_INTEGER, session->userid, | |
1649 "lid", MSIM_TYPE_INTEGER, MG_OWN_IM_INFO_LID, | |
1650 "rid", MSIM_TYPE_INTEGER, session->next_rid++, | |
1651 "body", MSIM_TYPE_STRING, g_strdup(""), | |
1652 NULL); | |
1653 | |
1654 /* TODO: set options (persist cmd=514,dsn=1,lid=10) */ | |
1655 /* TODO: set blocklist */ | |
1656 | |
1657 /* Notify servers of our current status. */ | |
1658 purple_debug_info("msim", "msim_we_are_logged_on: notifying servers of status\n"); | |
1659 msim_set_status(session->account, | |
1660 purple_account_get_active_status(session->account)); | |
1661 | |
1662 /* TODO: setinfo */ | |
1663 /* | |
1664 body = msim_msg_new( | |
1665 "TotalFriends", MSIM_TYPE_INTEGER, 666, | |
1666 NULL); | |
1667 msim_send(session, | |
1668 "setinfo", MSIM_TYPE_BOOLEAN, TRUE, | |
1669 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
1670 "info", MSIM_TYPE_DICTIONARY, body, | |
1671 NULL); | |
1672 */ | |
1673 | |
1674 /* Disable due to problems with timeouts. TODO: fix. */ | |
1675 #ifdef MSIM_USE_KEEPALIVE | |
1676 purple_timeout_add(MSIM_KEEPALIVE_INTERVAL_CHECK, | |
1677 (GSourceFunc)msim_check_alive, session); | |
1678 #endif | |
1679 | |
1680 /* Check mail if they want to. */ | |
1681 if (purple_account_get_check_mail(session->account)) { | |
1682 session->inbox_handle = purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK, | |
1683 (GSourceFunc)msim_check_inbox, session); | |
1684 msim_check_inbox(session); | |
1685 } | |
1686 | |
1687 msim_get_contact_list(session, MSIM_CONTACT_LIST_INITIAL_FRIENDS); | |
1688 | |
1689 return TRUE; | |
1690 } | |
1691 | |
1692 /** | |
1693 * Process a message. | |
1694 * | 1852 * |
1695 * @param session | 1853 * @param session |
1696 * @param msg A message from the server, ready for processing (possibly with resolved username information attached). Caller frees. | 1854 * @param msg A message from the server, ready for processing (possibly with resolved username information attached). Caller frees. |
1697 * | 1855 * |
1698 * @return TRUE if successful. FALSE if processing failed. | 1856 * @return TRUE if successful. FALSE if processing failed. |
1699 */ | 1857 */ |
1700 static gboolean | 1858 static gboolean |
1701 msim_process(MsimSession *session, MsimMessage *msg) | 1859 msim_process(MsimSession *session, MsimMessage *msg) |
1702 { | 1860 { |
1703 g_return_val_if_fail(session != NULL, FALSE); | 1861 g_return_val_if_fail(session != NULL, FALSE); |
1704 g_return_val_if_fail(msg != NULL, FALSE); | 1862 g_return_val_if_fail(msg != NULL, FALSE); |
1705 | |
1706 #ifdef MSIM_DEBUG_MSG | |
1707 msim_msg_dump("ready to process: %s\n", msg); | |
1708 #endif | |
1709 | 1863 |
1710 if (msim_msg_get_integer(msg, "lc") == 1) { | 1864 if (msim_msg_get_integer(msg, "lc") == 1) { |
1711 return msim_login_challenge(session, msg); | 1865 return msim_login_challenge(session, msg); |
1712 } else if (msim_msg_get_integer(msg, "lc") == 2) { | 1866 } else if (msim_msg_get_integer(msg, "lc") == 2) { |
1713 /* return msim_we_are_logged_on(session, msg); */ | 1867 /* return msim_we_are_logged_on(session, msg); */ |
1730 msim_unrecognized(session, msg, "in msim_process"); | 1884 msim_unrecognized(session, msg, "in msim_process"); |
1731 return FALSE; | 1885 return FALSE; |
1732 } | 1886 } |
1733 } | 1887 } |
1734 | 1888 |
1735 /** Process the initial server information from the server. */ | 1889 /** |
1890 * After a uid is resolved to username, tag it with the username and submit for processing. | |
1891 * | |
1892 * @param session | |
1893 * @param userinfo Response messsage to resolving request. | |
1894 * @param data MsimMessage *, the message to attach information to. | |
1895 */ | |
1896 static void | |
1897 msim_incoming_resolved(MsimSession *session, const MsimMessage *userinfo, | |
1898 gpointer data) | |
1899 { | |
1900 gchar *username; | |
1901 MsimMessage *msg, *body; | |
1902 | |
1903 g_return_if_fail(MSIM_SESSION_VALID(session)); | |
1904 g_return_if_fail(userinfo != NULL); | |
1905 | |
1906 body = msim_msg_get_dictionary(userinfo, "body"); | |
1907 g_return_if_fail(body != NULL); | |
1908 | |
1909 username = msim_msg_get_string(body, "UserName"); | |
1910 g_return_if_fail(username != NULL); | |
1911 /* Note: username will be owned by 'msg' below. */ | |
1912 | |
1913 msg = (MsimMessage *)data; | |
1914 g_return_if_fail(msg != NULL); | |
1915 | |
1916 /* TODO: more elegant solution than below. attach whole message? */ | |
1917 /* Special elements name beginning with '_', we'll use internally within the | |
1918 * program (did not come directly from the wire). */ | |
1919 msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, username); /* This makes 'msg' the owner of 'username' */ | |
1920 | |
1921 /* TODO: attach more useful information, like ImageURL */ | |
1922 | |
1923 msim_process(session, msg); | |
1924 | |
1925 msim_msg_free(msg); | |
1926 msim_msg_free(body); | |
1927 } | |
1928 | |
1929 /** | |
1930 * Preprocess incoming messages, resolving as needed, calling | |
1931 * msim_process() when ready to process. | |
1932 * | |
1933 * @param session | |
1934 * @param msg MsimMessage *, freed by caller. | |
1935 */ | |
1736 static gboolean | 1936 static gboolean |
1737 msim_process_server_info(MsimSession *session, MsimMessage *msg) | 1937 msim_preprocess_incoming(MsimSession *session, MsimMessage *msg) |
1738 { | 1938 { |
1739 MsimMessage *body; | |
1740 | |
1741 body = msim_msg_get_dictionary(msg, "body"); | |
1742 g_return_val_if_fail(body != NULL, FALSE); | |
1743 | |
1744 /* Example body: | |
1745 AdUnitRefreshInterval=10. | |
1746 AlertPollInterval=360. | |
1747 AllowChatRoomEmoticonSharing=False. | |
1748 ChatRoomUserIDs=78744676;163733130;1300326231;123521495;142663391. | |
1749 CurClientVersion=673. | |
1750 EnableIMBrowse=True. | |
1751 EnableIMStuffAvatars=False. | |
1752 EnableIMStuffZaps=False. | |
1753 MaxAddAllFriends=100. | |
1754 MaxContacts=1000. | |
1755 MinClientVersion=594. | |
1756 MySpaceIM_ENGLISH=78744676. | |
1757 MySpaceNowTimer=720. | |
1758 PersistenceDataTimeout=900. | |
1759 UseWebChallenge=1. | |
1760 WebTicketGoHome=False | |
1761 | |
1762 Anything useful? TODO: use what is useful, and use it. | |
1763 */ | |
1764 purple_debug_info("msim_process_server_info", | |
1765 "maximum contacts: %d\n", | |
1766 msim_msg_get_integer(body, "MaxContacts")); | |
1767 | |
1768 session->server_info = body; | |
1769 /* session->server_info freed in msim_session_destroy */ | |
1770 | |
1771 return TRUE; | |
1772 } | |
1773 | |
1774 /** Process a web challenge, used to login to the web site. */ | |
1775 static gboolean | |
1776 msim_web_challenge(MsimSession *session, MsimMessage *msg) | |
1777 { | |
1778 /* TODO: web challenge, store token. #2659. */ | |
1779 return FALSE; | |
1780 } | |
1781 | |
1782 /** | |
1783 * Process a persistance message reply from the server. | |
1784 * | |
1785 * @param session | |
1786 * @param msg Message reply from server. | |
1787 * | |
1788 * @return TRUE if successful. | |
1789 * | |
1790 * msim_lookup_user sets callback for here | |
1791 */ | |
1792 static gboolean | |
1793 msim_process_reply(MsimSession *session, MsimMessage *msg) | |
1794 { | |
1795 MSIM_USER_LOOKUP_CB cb; | |
1796 gpointer data; | |
1797 guint rid, cmd, dsn, lid; | |
1798 | |
1799 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | 1939 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); |
1800 g_return_val_if_fail(msg != NULL, FALSE); | 1940 g_return_val_if_fail(msg != NULL, FALSE); |
1801 | 1941 |
1802 msim_store_user_info(session, msg, NULL); | 1942 if (msim_msg_get(msg, "bm") && msim_msg_get(msg, "f")) { |
1803 | 1943 guint uid; |
1804 rid = msim_msg_get_integer(msg, "rid"); | 1944 const gchar *username; |
1805 cmd = msim_msg_get_integer(msg, "cmd"); | 1945 |
1806 dsn = msim_msg_get_integer(msg, "dsn"); | 1946 /* 'f' = userid message is from, in buddy messages */ |
1807 lid = msim_msg_get_integer(msg, "lid"); | 1947 uid = msim_msg_get_integer(msg, "f"); |
1808 | 1948 |
1809 /* Unsolicited messages */ | 1949 username = msim_uid2username_from_blist(session->account, uid); |
1810 if (cmd == (MSIM_CMD_BIT_REPLY | MSIM_CMD_GET)) { | 1950 |
1811 if (dsn == MG_SERVER_INFO_DSN && lid == MG_SERVER_INFO_LID) { | 1951 if (username) { |
1812 return msim_process_server_info(session, msg); | 1952 /* Know username already, use it. */ |
1813 } else if (dsn == MG_WEB_CHALLENGE_DSN && lid == MG_WEB_CHALLENGE_LID) { | 1953 purple_debug_info("msim", "msim_preprocess_incoming: tagging with _username=%s\n", |
1814 return msim_web_challenge(session, msg); | 1954 username); |
1815 } | 1955 msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username)); |
1816 } | 1956 return msim_process(session, msg); |
1817 | 1957 |
1818 /* If a callback is registered for this userid lookup, call it. */ | |
1819 cb = g_hash_table_lookup(session->user_lookup_cb, GUINT_TO_POINTER(rid)); | |
1820 data = g_hash_table_lookup(session->user_lookup_cb_data, GUINT_TO_POINTER(rid)); | |
1821 | |
1822 if (cb) { | |
1823 purple_debug_info("msim", "msim_process_reply: calling callback now\n"); | |
1824 msim_msg_dump("for msg=%s\n", msg); | |
1825 /* Clone message, so that the callback 'cb' can use it (needs to free it also). */ | |
1826 cb(session, msim_msg_clone(msg), data); | |
1827 g_hash_table_remove(session->user_lookup_cb, GUINT_TO_POINTER(rid)); | |
1828 g_hash_table_remove(session->user_lookup_cb_data, GUINT_TO_POINTER(rid)); | |
1829 } else { | |
1830 purple_debug_info("msim", | |
1831 "msim_process_reply: no callback for rid %d\n", rid); | |
1832 } | |
1833 | |
1834 return TRUE; | |
1835 } | |
1836 | |
1837 /** | |
1838 * Handle an error from the server. | |
1839 * | |
1840 * @param session | |
1841 * @param msg The message. | |
1842 * | |
1843 * @return TRUE if successfully reported error. | |
1844 */ | |
1845 static gboolean | |
1846 msim_error(MsimSession *session, MsimMessage *msg) | |
1847 { | |
1848 gchar *errmsg, *full_errmsg; | |
1849 guint err; | |
1850 | |
1851 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
1852 g_return_val_if_fail(msg != NULL, FALSE); | |
1853 | |
1854 err = msim_msg_get_integer(msg, "err"); | |
1855 errmsg = msim_msg_get_string(msg, "errmsg"); | |
1856 | |
1857 full_errmsg = g_strdup_printf(_("Protocol error, code %d: %s"), err, | |
1858 errmsg ? errmsg : "no 'errmsg' given"); | |
1859 | |
1860 g_free(errmsg); | |
1861 | |
1862 purple_debug_info("msim", "msim_error (sesskey=%d): %s\n", | |
1863 session->sesskey, full_errmsg); | |
1864 | |
1865 /* Destroy session if fatal. */ | |
1866 if (msim_msg_get(msg, "fatal")) { | |
1867 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; | |
1868 purple_debug_info("msim", "fatal error, closing\n"); | |
1869 | |
1870 switch (err) { | |
1871 case MSIM_ERROR_INCORRECT_PASSWORD: /* Incorrect password */ | |
1872 reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; | |
1873 if (!purple_account_get_remember_password(session->account)) | |
1874 purple_account_set_password(session->account, NULL); | |
1875 #ifdef MSIM_MAX_PASSWORD_LENGTH | |
1876 if (session->account->password && (strlen(session->account->password) > MSIM_MAX_PASSWORD_LENGTH)) { | |
1877 gchar *suggestion; | |
1878 | |
1879 suggestion = g_strdup_printf(_("%s Your password is " | |
1880 "%d characters, greater than the " | |
1881 "expected maximum length of %d for " | |
1882 "MySpaceIM. Please shorten your " | |
1883 "password at http://profileedit.myspace.com/index.cfm?fuseaction=accountSettings.changePassword and try again."), | |
1884 full_errmsg, (int) | |
1885 strlen(session->account->password), | |
1886 MSIM_MAX_PASSWORD_LENGTH); | |
1887 | |
1888 /* Replace full_errmsg. */ | |
1889 g_free(full_errmsg); | |
1890 full_errmsg = suggestion; | |
1891 } | |
1892 #endif | |
1893 break; | |
1894 case MSIM_ERROR_LOGGED_IN_ELSEWHERE: /* Logged in elsewhere */ | |
1895 reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE; | |
1896 if (!purple_account_get_remember_password(session->account)) | |
1897 purple_account_set_password(session->account, NULL); | |
1898 break; | |
1899 } | |
1900 purple_connection_error_reason (session->gc, reason, full_errmsg); | |
1901 } else { | |
1902 purple_notify_error(session->account, _("MySpaceIM Error"), full_errmsg, NULL); | |
1903 } | |
1904 | |
1905 g_free(full_errmsg); | |
1906 | |
1907 return TRUE; | |
1908 } | |
1909 | |
1910 /** | |
1911 * Process incoming status messages. | |
1912 * | |
1913 * @param session | |
1914 * @param msg Status update message. Caller frees. | |
1915 * | |
1916 * @return TRUE if successful. | |
1917 */ | |
1918 static gboolean | |
1919 msim_incoming_status(MsimSession *session, MsimMessage *msg) | |
1920 { | |
1921 PurpleBuddyList *blist; | |
1922 MsimUser *user; | |
1923 GList *list; | |
1924 gchar *status_headline, *status_headline_escaped; | |
1925 gint status_code, purple_status_code; | |
1926 gchar *username; | |
1927 gchar *unrecognized_msg; | |
1928 | |
1929 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
1930 g_return_val_if_fail(msg != NULL, FALSE); | |
1931 | |
1932 msim_msg_dump("msim_status msg=%s\n", msg); | |
1933 | |
1934 /* Helpfully looked up by msim_incoming_resolve() for us. */ | |
1935 username = msim_msg_get_string(msg, "_username"); | |
1936 g_return_val_if_fail(username != NULL, FALSE); | |
1937 | |
1938 { | |
1939 gchar *ss; | |
1940 | |
1941 ss = msim_msg_get_string(msg, "msg"); | |
1942 purple_debug_info("msim", | |
1943 "msim_status: updating status for <%s> to <%s>\n", | |
1944 username, ss ? ss : "(NULL)"); | |
1945 g_free(ss); | |
1946 } | |
1947 | |
1948 /* Example fields: | |
1949 * |s|0|ss|Offline | |
1950 * |s|1|ss|:-)|ls||ip|0|p|0 | |
1951 */ | |
1952 list = msim_msg_get_list(msg, "msg"); | |
1953 | |
1954 status_code = msim_msg_get_integer_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE)); | |
1955 purple_debug_info("msim", "msim_status: %s's status code = %d\n", username, status_code); | |
1956 status_headline = msim_msg_get_string_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE)); | |
1957 | |
1958 blist = purple_get_blist(); | |
1959 | |
1960 /* Add buddy if not found. | |
1961 * TODO: Could this be responsible for #3444? */ | |
1962 user = msim_find_user(session, username); | |
1963 if (!user) { | |
1964 PurpleBuddy *buddy; | |
1965 | |
1966 purple_debug_info("msim", | |
1967 "msim_status: making new buddy for %s\n", username); | |
1968 buddy = purple_buddy_new(session->account, username, NULL); | |
1969 purple_blist_add_buddy(buddy, NULL, NULL, NULL); | |
1970 | |
1971 user = msim_get_user_from_buddy(buddy); | |
1972 user->id = msim_msg_get_integer(msg, "f"); | |
1973 | |
1974 /* Keep track of the user ID across sessions */ | |
1975 purple_blist_node_set_int(&buddy->node, "UserID", user->id); | |
1976 | |
1977 msim_store_user_info(session, msg, NULL); | |
1978 } else { | |
1979 purple_debug_info("msim", "msim_status: found buddy %s\n", username); | |
1980 } | |
1981 | |
1982 if (status_headline && strcmp(status_headline, "") != 0) { | |
1983 /* The status headline is plaintext, but libpurple treats it as HTML, | |
1984 * so escape any HTML characters to their entity equivalents. */ | |
1985 status_headline_escaped = g_markup_escape_text(status_headline, strlen(status_headline)); | |
1986 } else { | |
1987 status_headline_escaped = NULL; | |
1988 } | |
1989 | |
1990 g_free(status_headline); | |
1991 | |
1992 if (user->headline) | |
1993 g_free(user->headline); | |
1994 | |
1995 /* don't copy; let the MsimUser own the headline, memory-wise */ | |
1996 user->headline = status_headline_escaped; | |
1997 | |
1998 /* Set user status */ | |
1999 switch (status_code) { | |
2000 case MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN: | |
2001 purple_status_code = PURPLE_STATUS_OFFLINE; | |
2002 break; | |
2003 | |
2004 case MSIM_STATUS_CODE_ONLINE: | |
2005 purple_status_code = PURPLE_STATUS_AVAILABLE; | |
2006 break; | |
2007 | |
2008 case MSIM_STATUS_CODE_AWAY: | |
2009 purple_status_code = PURPLE_STATUS_AWAY; | |
2010 break; | |
2011 | |
2012 case MSIM_STATUS_CODE_IDLE: | |
2013 /* Treat idle as an available status. */ | |
2014 purple_status_code = PURPLE_STATUS_AVAILABLE; | |
2015 break; | |
2016 | |
2017 default: | |
2018 purple_debug_info("msim", "msim_incoming_status for %s, unknown status code %d, treating as available\n", | |
2019 username, status_code); | |
2020 purple_status_code = PURPLE_STATUS_AVAILABLE; | |
2021 | |
2022 unrecognized_msg = g_strdup_printf("msim_incoming_status, unrecognized status code: %d\n", | |
2023 status_code); | |
2024 msim_unrecognized(session, NULL, unrecognized_msg); | |
2025 g_free(unrecognized_msg); | |
2026 | |
2027 } | |
2028 | |
2029 purple_prpl_got_user_status(session->account, username, purple_primitive_get_id_from_type(purple_status_code), NULL); | |
2030 | |
2031 if (status_code == MSIM_STATUS_CODE_IDLE) { | |
2032 purple_debug_info("msim", "msim_status: got idle: %s\n", username); | |
2033 purple_prpl_got_user_idle(session->account, username, TRUE, time(NULL)); | |
2034 } else { | |
2035 /* All other statuses indicate going back to non-idle. */ | |
2036 purple_prpl_got_user_idle(session->account, username, FALSE, time(NULL)); | |
2037 } | |
2038 | |
2039 #ifdef MSIM_SEND_CLIENT_VERSION | |
2040 if (status_code == MSIM_STATUS_CODE_ONLINE) { | |
2041 /* Secretly whisper to unofficial clients our own version as they come online */ | |
2042 msim_send_unofficial_client(session, username); | |
2043 } | |
2044 #endif | |
2045 | |
2046 if (status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN) { | |
2047 /* Get information when they come online. | |
2048 * TODO: periodically refresh? | |
2049 */ | |
2050 purple_debug_info("msim_incoming_status", "%s came online, looking up\n", username); | |
2051 msim_lookup_user(session, username, NULL, NULL); | |
2052 } | |
2053 | |
2054 g_free(username); | |
2055 msim_msg_list_free(list); | |
2056 | |
2057 return TRUE; | |
2058 } | |
2059 | |
2060 /** Add a buddy to user's buddy list. */ | |
2061 void | |
2062 msim_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) | |
2063 { | |
2064 MsimSession *session; | |
2065 MsimMessage *msg; | |
2066 MsimMessage *msg_persist; | |
2067 MsimMessage *body; | |
2068 | |
2069 session = (MsimSession *)gc->proto_data; | |
2070 purple_debug_info("msim", "msim_add_buddy: want to add %s to %s\n", | |
2071 buddy->name, (group && group->name) ? group->name : "(no group)"); | |
2072 | |
2073 msg = msim_msg_new( | |
2074 "addbuddy", MSIM_TYPE_BOOLEAN, TRUE, | |
2075 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
2076 /* "newprofileid" will be inserted here with uid. */ | |
2077 "reason", MSIM_TYPE_STRING, g_strdup(""), | |
2078 NULL); | |
2079 | |
2080 if (!msim_postprocess_outgoing(session, msg, buddy->name, "newprofileid", "reason")) { | |
2081 purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("'addbuddy' command failed.")); | |
2082 msim_msg_free(msg); | |
2083 return; | |
2084 } | |
2085 msim_msg_free(msg); | |
2086 | |
2087 /* TODO: if addbuddy fails ('error' message is returned), delete added buddy from | |
2088 * buddy list since Purple adds it locally. */ | |
2089 | |
2090 body = msim_msg_new( | |
2091 "ContactID", MSIM_TYPE_STRING, g_strdup("<uid>"), | |
2092 "GroupName", MSIM_TYPE_STRING, g_strdup(group->name), | |
2093 "Position", MSIM_TYPE_INTEGER, 1000, | |
2094 "Visibility", MSIM_TYPE_INTEGER, 1, | |
2095 "NickName", MSIM_TYPE_STRING, g_strdup(""), | |
2096 "NameSelect", MSIM_TYPE_INTEGER, 0, | |
2097 NULL); | |
2098 | |
2099 /* TODO: Update blocklist. */ | |
2100 | |
2101 msg_persist = msim_msg_new( | |
2102 "persist", MSIM_TYPE_INTEGER, 1, | |
2103 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
2104 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_PUT, | |
2105 "dsn", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_DSN, | |
2106 "uid", MSIM_TYPE_INTEGER, session->userid, | |
2107 "lid", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_LID, | |
2108 /* TODO: Use msim_new_reply_callback to get rid. */ | |
2109 "rid", MSIM_TYPE_INTEGER, session->next_rid++, | |
2110 "body", MSIM_TYPE_DICTIONARY, body, | |
2111 NULL); | |
2112 | |
2113 if (!msim_postprocess_outgoing(session, msg_persist, buddy->name, "body", NULL)) | |
2114 { | |
2115 purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("persist command failed")); | |
2116 msim_msg_free(msg_persist); | |
2117 return; | |
2118 } | |
2119 msim_msg_free(msg_persist); | |
2120 | |
2121 } | |
2122 | |
2123 /** Perform actual postprocessing on a message, adding userid as specified. | |
2124 * | |
2125 * @param msg The message to postprocess. | |
2126 * @param uid_before Name of field where to insert new field before, or NULL for end. | |
2127 * @param uid_field_name Name of field to add uid to. | |
2128 * @param uid The userid to insert. | |
2129 * | |
2130 * If the field named by uid_field_name already exists, then its string contents will | |
2131 * be used for the field, except "<uid>" will be replaced by the userid. | |
2132 * | |
2133 * If the field named by uid_field_name does not exist, it will be added before the | |
2134 * field named by uid_before, as an integer, with the userid. | |
2135 * | |
2136 * Does not handle sending, or scheduling userid lookup. For that, see msim_postprocess_outgoing(). | |
2137 */ | |
2138 static MsimMessage * | |
2139 msim_do_postprocessing(MsimMessage *msg, const gchar *uid_before, | |
2140 const gchar *uid_field_name, guint uid) | |
2141 { | |
2142 MsimMessageElement *elem; | |
2143 msim_msg_dump("msim_do_postprocessing msg: %s\n", msg); | |
2144 | |
2145 /* First, check - if the field already exists, replace <uid> within it */ | |
2146 if ((elem = msim_msg_get(msg, uid_field_name)) != NULL) { | |
2147 gchar *fmt_string; | |
2148 gchar *uid_str, *new_str; | |
2149 | |
2150 /* Get the packed element, flattening it. This allows <uid> to be | |
2151 * replaced within nested data structures, since the replacement is done | |
2152 * on the linear, packed data, not on a complicated data structure. | |
2153 * | |
2154 * For example, if the field was originally a dictionary or a list, you | |
2155 * would have to iterate over all the items in it to see what needs to | |
2156 * be replaced. But by packing it first, the <uid> marker is easily replaced | |
2157 * just by a string replacement. | |
2158 */ | |
2159 fmt_string = msim_msg_pack_element_data(elem); | |
2160 | |
2161 uid_str = g_strdup_printf("%d", uid); | |
2162 new_str = purple_strreplace(fmt_string, "<uid>", uid_str); | |
2163 g_free(uid_str); | |
2164 g_free(fmt_string); | |
2165 | |
2166 /* Free the old element data */ | |
2167 msim_msg_free_element_data(elem->data); | |
2168 | |
2169 /* Replace it with our new data */ | |
2170 elem->data = new_str; | |
2171 elem->type = MSIM_TYPE_RAW; | |
2172 | |
2173 } else { | |
2174 /* Otherwise, insert new field into outgoing message. */ | |
2175 msg = msim_msg_insert_before(msg, uid_before, uid_field_name, MSIM_TYPE_INTEGER, GUINT_TO_POINTER(uid)); | |
2176 } | |
2177 | |
2178 msim_msg_dump("msim_postprocess_outgoing_cb: postprocessed msg=%s\n", msg); | |
2179 | |
2180 return msg; | |
2181 } | |
2182 | |
2183 /** Callback for msim_postprocess_outgoing() to add a userid to a message, and send it (once receiving userid). | |
2184 * | |
2185 * @param session | |
2186 * @param userinfo The user information reply message, containing the user ID | |
2187 * @param data The message to postprocess and send. | |
2188 * | |
2189 * The data message should contain these fields: | |
2190 * | |
2191 * _uid_field_name: string, name of field to add with userid from userinfo message | |
2192 * _uid_before: string, name of field before field to insert, or NULL for end | |
2193 * | |
2194 * | |
2195 */ | |
2196 static void | |
2197 msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, | |
2198 gpointer data) | |
2199 { | |
2200 gchar *uid_field_name, *uid_before, *username; | |
2201 guint uid; | |
2202 MsimMessage *msg, *body; | |
2203 | |
2204 msg = (MsimMessage *)data; | |
2205 | |
2206 msim_msg_dump("msim_postprocess_outgoing_cb() got msg=%s\n", msg); | |
2207 | |
2208 /* Obtain userid from userinfo message. */ | |
2209 body = msim_msg_get_dictionary(userinfo, "body"); | |
2210 g_return_if_fail(body != NULL); | |
2211 | |
2212 uid = msim_msg_get_integer(body, "UserID"); | |
2213 msim_msg_free(body); | |
2214 | |
2215 username = msim_msg_get_string(msg, "_username"); | |
2216 | |
2217 if (!uid) { | |
2218 gchar *msg; | |
2219 | |
2220 msg = g_strdup_printf(_("No such user: %s"), username); | |
2221 if (!purple_conv_present_error(username, session->account, msg)) { | |
2222 purple_notify_error(NULL, NULL, _("User lookup"), msg); | |
2223 } | |
2224 | |
2225 g_free(msg); | |
2226 g_free(username); | |
2227 /* TODO: free | |
2228 * msim_msg_free(msg); | |
2229 */ | |
2230 return; | |
2231 } | |
2232 | |
2233 uid_field_name = msim_msg_get_string(msg, "_uid_field_name"); | |
2234 uid_before = msim_msg_get_string(msg, "_uid_before"); | |
2235 | |
2236 msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid); | |
2237 | |
2238 /* Send */ | |
2239 if (!msim_msg_send(session, msg)) { | |
2240 msim_msg_dump("msim_postprocess_outgoing_cb: sending failed for message: %s\n", msg); | |
2241 } | |
2242 | |
2243 | |
2244 /* Free field names AFTER sending message, because MsimMessage does NOT copy | |
2245 * field names - instead, treats them as static strings (which they usually are). | |
2246 */ | |
2247 g_free(uid_field_name); | |
2248 g_free(uid_before); | |
2249 g_free(username); | |
2250 /* TODO: free | |
2251 * msim_msg_free(msg); | |
2252 */ | |
2253 } | |
2254 | |
2255 /** Postprocess and send a message. | |
2256 * | |
2257 * @param session | |
2258 * @param msg Message to postprocess. Will NOT be freed. | |
2259 * @param username Username to resolve. Assumed to be a static string (will not be freed or copied). | |
2260 * @param uid_field_name Name of new field to add, containing uid of username. Static string. | |
2261 * @param uid_before Name of existing field to insert username field before. Static string. | |
2262 * | |
2263 * @return TRUE if successful. | |
2264 */ | |
2265 gboolean | |
2266 msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, | |
2267 const gchar *username, const gchar *uid_field_name, | |
2268 const gchar *uid_before) | |
2269 { | |
2270 PurpleBuddy *buddy; | |
2271 guint uid; | |
2272 gboolean rc; | |
2273 | |
2274 g_return_val_if_fail(msg != NULL, FALSE); | |
2275 | |
2276 /* Store information for msim_postprocess_outgoing_cb(). */ | |
2277 msim_msg_dump("msim_postprocess_outgoing: msg before=%s\n", msg); | |
2278 msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username)); | |
2279 msg = msim_msg_append(msg, "_uid_field_name", MSIM_TYPE_STRING, g_strdup(uid_field_name)); | |
2280 msg = msim_msg_append(msg, "_uid_before", MSIM_TYPE_STRING, g_strdup(uid_before)); | |
2281 | |
2282 /* First, try the most obvious. If numeric userid is given, use that directly. */ | |
2283 if (msim_is_userid(username)) { | |
2284 uid = atol(username); | |
2285 } else { | |
2286 /* Next, see if on buddy list and know uid. */ | |
2287 buddy = purple_find_buddy(session->account, username); | |
2288 if (buddy) { | |
2289 uid = purple_blist_node_get_int(&buddy->node, "UserID"); | |
2290 } else { | 1958 } else { |
2291 uid = 0; | 1959 gchar *from; |
2292 } | 1960 |
2293 | 1961 /* Send lookup request. */ |
2294 if (!buddy || !uid) { | 1962 /* XXX: where is msim_msg_get_string() freed? make _strdup and _nonstrdup. */ |
2295 /* Don't have uid offhand - need to ask for it, and wait until hear back before sending. */ | 1963 purple_debug_info("msim", "msim_incoming: sending lookup, setting up callback\n"); |
2296 purple_debug_info("msim", ">>> msim_postprocess_outgoing: couldn't find username %s in blist\n", | 1964 from = msim_msg_get_string(msg, "f"); |
2297 username ? username : "(NULL)"); | 1965 msim_lookup_user(session, from, msim_incoming_resolved, msim_msg_clone(msg)); |
2298 msim_msg_dump("msim_postprocess_outgoing - scheduling lookup, msg=%s\n", msg); | 1966 g_free(from); |
2299 /* TODO: where is cloned message freed? Should be in _cb. */ | 1967 |
2300 msim_lookup_user(session, username, msim_postprocess_outgoing_cb, msim_msg_clone(msg)); | 1968 /* indeterminate */ |
2301 return TRUE; /* not sure of status yet - haven't sent! */ | 1969 return TRUE; |
2302 } | |
2303 } | |
2304 | |
2305 /* Already have uid, postprocess and send msg immediately. */ | |
2306 purple_debug_info("msim", "msim_postprocess_outgoing: found username %s has uid %d\n", | |
2307 username ? username : "(NULL)", uid); | |
2308 | |
2309 msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid); | |
2310 | |
2311 msim_msg_dump("msim_postprocess_outgoing: msg after (uid immediate)=%s\n", msg); | |
2312 | |
2313 rc = msim_msg_send(session, msg); | |
2314 | |
2315 /* TODO: free | |
2316 * msim_msg_free(msg); | |
2317 */ | |
2318 | |
2319 return rc; | |
2320 } | |
2321 | |
2322 /** Remove a buddy from the user's buddy list. */ | |
2323 void | |
2324 msim_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) | |
2325 { | |
2326 MsimSession *session; | |
2327 MsimMessage *delbuddy_msg; | |
2328 MsimMessage *persist_msg; | |
2329 MsimMessage *blocklist_msg; | |
2330 GList *blocklist_updates; | |
2331 | |
2332 session = (MsimSession *)gc->proto_data; | |
2333 | |
2334 delbuddy_msg = msim_msg_new( | |
2335 "delbuddy", MSIM_TYPE_BOOLEAN, TRUE, | |
2336 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
2337 /* 'delprofileid' with uid will be inserted here. */ | |
2338 NULL); | |
2339 | |
2340 if (!msim_postprocess_outgoing(session, delbuddy_msg, buddy->name, "delprofileid", NULL)) { | |
2341 purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("'delbuddy' command failed")); | |
2342 msim_msg_free(delbuddy_msg); | |
2343 return; | |
2344 } | |
2345 msim_msg_free(delbuddy_msg); | |
2346 | |
2347 persist_msg = msim_msg_new( | |
2348 "persist", MSIM_TYPE_INTEGER, 1, | |
2349 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
2350 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_DELETE, | |
2351 "dsn", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_DSN, | |
2352 "lid", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_LID, | |
2353 "uid", MSIM_TYPE_INTEGER, session->userid, | |
2354 "rid", MSIM_TYPE_INTEGER, session->next_rid++, | |
2355 /* <uid> will be replaced by postprocessing */ | |
2356 "body", MSIM_TYPE_STRING, g_strdup("ContactID=<uid>"), | |
2357 NULL); | |
2358 | |
2359 if (!msim_postprocess_outgoing(session, persist_msg, buddy->name, "body", NULL)) { | |
2360 purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("persist command failed")); | |
2361 msim_msg_free(persist_msg); | |
2362 return; | |
2363 } | |
2364 msim_msg_free(persist_msg); | |
2365 | |
2366 blocklist_updates = NULL; | |
2367 blocklist_updates = g_list_prepend(blocklist_updates, "a-"); | |
2368 blocklist_updates = g_list_prepend(blocklist_updates, "<uid>"); | |
2369 blocklist_updates = g_list_prepend(blocklist_updates, "b-"); | |
2370 blocklist_updates = g_list_prepend(blocklist_updates, "<uid>"); | |
2371 blocklist_updates = g_list_reverse(blocklist_updates); | |
2372 | |
2373 blocklist_msg = msim_msg_new( | |
2374 "blocklist", MSIM_TYPE_BOOLEAN, TRUE, | |
2375 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
2376 /* TODO: MsimMessage lists. Currently <uid> isn't replaced in lists. */ | |
2377 /* "idlist", MSIM_TYPE_STRING, g_strdup("a-|<uid>|b-|<uid>"), */ | |
2378 "idlist", MSIM_TYPE_LIST, blocklist_updates, | |
2379 NULL); | |
2380 | |
2381 if (!msim_postprocess_outgoing(session, blocklist_msg, buddy->name, "idlist", NULL)) { | |
2382 purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("blocklist command failed")); | |
2383 msim_msg_free(blocklist_msg); | |
2384 return; | |
2385 } | |
2386 msim_msg_free(blocklist_msg); | |
2387 } | |
2388 | |
2389 /** | |
2390 * Returns a string of a username in canonical form. Basically removes all the | |
2391 * spaces, lowercases the string, and looks up user IDs to usernames. | |
2392 * Normalizing tom, TOM, Tom, and 6221 wil all return 'tom'. | |
2393 * | |
2394 * Borrowed this code from oscar_normalize. Added checking for | |
2395 * "if userid, get name before normalizing" | |
2396 */ | |
2397 const char *msim_normalize(const PurpleAccount *account, const char *str) { | |
2398 static char normalized[BUF_LEN]; | |
2399 char *tmp1, *tmp2; | |
2400 int i, j; | |
2401 guint id; | |
2402 | |
2403 g_return_val_if_fail(str != NULL, NULL); | |
2404 | |
2405 if (msim_is_userid(str)) { | |
2406 /* Have user ID, we need to get their username first :) */ | |
2407 const char *username; | |
2408 | |
2409 /* If the account does not exist, we can't look up the user. */ | |
2410 if (!account || !account->gc) | |
2411 return str; | |
2412 | |
2413 id = atol(str); | |
2414 username = msim_uid2username_from_blist((PurpleAccount *)account, id); | |
2415 if (!username) { | |
2416 /* Not in buddy list... scheisse... TODO: Manual Lookup! Bug #4631 */ | |
2417 /* Note: manual lookup using msim_lookup_user() is a problem inside | |
2418 * msim_normalize(), because msim_lookup_user() calls a callback function | |
2419 * when the user information has been looked up, but msim_normalize() expects | |
2420 * the result immediately. */ | |
2421 strncpy(normalized, str, BUF_LEN); | |
2422 } else { | |
2423 strncpy(normalized, username, BUF_LEN); | |
2424 } | 1970 } |
2425 } else { | 1971 } else { |
2426 /* Have username. */ | 1972 /* Nothing to resolve - send directly to processing. */ |
2427 strncpy(normalized, str, BUF_LEN); | 1973 return msim_process(session, msg); |
2428 } | 1974 } |
2429 | |
2430 /* Strip spaces. */ | |
2431 for (i=0, j=0; normalized[j]; i++, j++) { | |
2432 while (normalized[j] == ' ') | |
2433 j++; | |
2434 normalized[i] = normalized[j]; | |
2435 } | |
2436 normalized[i] = '\0'; | |
2437 | |
2438 /* Lowercase and perform UTF-8 normalization. */ | |
2439 tmp1 = g_utf8_strdown(normalized, -1); | |
2440 tmp2 = g_utf8_normalize(tmp1, -1, G_NORMALIZE_DEFAULT); | |
2441 g_snprintf(normalized, sizeof(normalized), "%s", tmp2); | |
2442 g_free(tmp2); | |
2443 g_free(tmp1); | |
2444 | |
2445 /* TODO: re-add caps and spacing back to what the user wanted. | |
2446 * User can format their own names, for example 'msimprpl' is shown | |
2447 * as 'MsIm PrPl' in the official client. | |
2448 * | |
2449 * TODO: file a ticket to add this enhancement. | |
2450 */ | |
2451 | |
2452 return normalized; | |
2453 } | |
2454 | |
2455 static GHashTable * | |
2456 msim_get_account_text_table(PurpleAccount *unused) | |
2457 { | |
2458 GHashTable *table; | |
2459 | |
2460 table = g_hash_table_new(g_str_hash, g_str_equal); | |
2461 | |
2462 g_hash_table_insert(table, "login_label", (gpointer)_("Email Address...")); | |
2463 | |
2464 return table; | |
2465 } | |
2466 | |
2467 /** Return whether the buddy can be messaged while offline. | |
2468 * | |
2469 * The protocol supports offline messages in just the same way as online | |
2470 * messages. | |
2471 */ | |
2472 gboolean | |
2473 msim_offline_message(const PurpleBuddy *buddy) | |
2474 { | |
2475 return TRUE; | |
2476 } | 1975 } |
2477 | 1976 |
2478 /** | 1977 /** |
2479 * Callback when input available. | 1978 * Callback when input available. |
2480 * | 1979 * |
2482 * @param source File descriptor. | 1981 * @param source File descriptor. |
2483 * @param cond PURPLE_INPUT_READ | 1982 * @param cond PURPLE_INPUT_READ |
2484 * | 1983 * |
2485 * Reads the input, and calls msim_preprocess_incoming() to handle it. | 1984 * Reads the input, and calls msim_preprocess_incoming() to handle it. |
2486 */ | 1985 */ |
2487 static void | 1986 static void |
2488 msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond) | 1987 msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond) |
2489 { | 1988 { |
2490 PurpleConnection *gc; | 1989 PurpleConnection *gc; |
2491 PurpleAccount *account; | 1990 PurpleAccount *account; |
2492 MsimSession *session; | 1991 MsimSession *session; |
2515 /* Mark down that we got data, so we don't timeout. */ | 2014 /* Mark down that we got data, so we don't timeout. */ |
2516 session->last_comm = time(NULL); | 2015 session->last_comm = time(NULL); |
2517 | 2016 |
2518 /* If approaching end of buffer, reallocate some more memory. */ | 2017 /* If approaching end of buffer, reallocate some more memory. */ |
2519 if (session->rxsize < session->rxoff + MSIM_READ_BUF_SIZE) { | 2018 if (session->rxsize < session->rxoff + MSIM_READ_BUF_SIZE) { |
2520 purple_debug_info("msim", | 2019 purple_debug_info("msim", |
2521 "msim_input_cb: %d-byte read buffer full, rxoff=%d, " "growing by %d bytes\n", | 2020 "msim_input_cb: %d-byte read buffer full, rxoff=%d, " "growing by %d bytes\n", |
2522 session->rxsize, session->rxoff, MSIM_READ_BUF_SIZE); | 2021 session->rxsize, session->rxoff, MSIM_READ_BUF_SIZE); |
2523 session->rxsize += MSIM_READ_BUF_SIZE; | 2022 session->rxsize += MSIM_READ_BUF_SIZE; |
2524 session->rxbuf = g_realloc(session->rxbuf, session->rxsize); | 2023 session->rxbuf = g_realloc(session->rxbuf, session->rxsize); |
2525 | 2024 |
2526 return; | 2025 return; |
2527 } | 2026 } |
2528 | 2027 |
2529 purple_debug_info("msim", "dynamic buffer at %d (max %d), reading up to %d\n", | 2028 purple_debug_info("msim", "dynamic buffer at %d (max %d), reading up to %d\n", |
2530 session->rxoff, session->rxsize, | 2029 session->rxoff, session->rxsize, |
2531 MSIM_READ_BUF_SIZE - session->rxoff - 1); | 2030 MSIM_READ_BUF_SIZE - session->rxoff - 1); |
2532 | 2031 |
2533 /* Read into buffer. On Win32, need recv() not read(). session->fd also holds | 2032 /* Read into buffer. On Win32, need recv() not read(). session->fd also holds |
2534 * the file descriptor, but it sometimes differs from the 'source' parameter. | 2033 * the file descriptor, but it sometimes differs from the 'source' parameter. |
2535 */ | 2034 */ |
2536 n = recv(session->fd, | 2035 n = recv(session->fd, |
2537 session->rxbuf + session->rxoff, | 2036 session->rxbuf + session->rxoff, |
2538 session->rxsize - session->rxoff - 1, 0); | 2037 session->rxsize - session->rxoff - 1, 0); |
2539 | 2038 |
2540 if (n < 0 && errno == EAGAIN) { | 2039 if (n < 0 && errno == EAGAIN) { |
2541 return; | 2040 return; |
2542 } else if (n < 0) { | 2041 } else if (n < 0) { |
2543 purple_debug_error("msim", "msim_input_cb: read error, ret=%d, " | 2042 purple_debug_error("msim", "msim_input_cb: read error, ret=%d, " |
2544 "error=%s, source=%d, fd=%d (%X))\n", | 2043 "error=%s, source=%d, fd=%d (%X))\n", |
2545 n, g_strerror(errno), source, session->fd, session->fd); | 2044 n, g_strerror(errno), source, session->fd, session->fd); |
2546 purple_connection_error_reason (gc, | 2045 purple_connection_error_reason (gc, |
2547 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, | 2046 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, |
2548 _("Read error")); | 2047 _("Read error")); |
2549 return; | 2048 return; |
2550 } else if (n == 0) { | 2049 } else if (n == 0) { |
2551 purple_debug_info("msim", "msim_input_cb: server disconnected\n"); | 2050 purple_debug_info("msim", "msim_input_cb: server disconnected\n"); |
2572 #ifdef MSIM_CHECK_EMBEDDED_NULLS | 2071 #ifdef MSIM_CHECK_EMBEDDED_NULLS |
2573 /* Check for embedded NULs. I don't handle them, and they shouldn't occur. */ | 2072 /* Check for embedded NULs. I don't handle them, and they shouldn't occur. */ |
2574 if (strlen(session->rxbuf + session->rxoff) != n) { | 2073 if (strlen(session->rxbuf + session->rxoff) != n) { |
2575 /* Occurs after login, but it is not a null byte. */ | 2074 /* Occurs after login, but it is not a null byte. */ |
2576 purple_debug_info("msim", "msim_input_cb: strlen=%d, but read %d bytes" | 2075 purple_debug_info("msim", "msim_input_cb: strlen=%d, but read %d bytes" |
2577 "--null byte encountered?\n", | 2076 "--null byte encountered?\n", |
2578 strlen(session->rxbuf + session->rxoff), n); | 2077 strlen(session->rxbuf + session->rxoff), n); |
2579 /*purple_connection_error_reason (gc, | 2078 /*purple_connection_error_reason (gc, |
2580 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, | 2079 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, |
2581 "Invalid message - null byte on input"); */ | 2080 "Invalid message - null byte on input"); */ |
2582 return; | 2081 return; |
2596 | 2095 |
2597 #ifdef MSIM_DEBUG_RXBUF | 2096 #ifdef MSIM_DEBUG_RXBUF |
2598 purple_debug_info("msim", "in loop: buf=<%s>\n", session->rxbuf); | 2097 purple_debug_info("msim", "in loop: buf=<%s>\n", session->rxbuf); |
2599 #endif | 2098 #endif |
2600 *end = 0; | 2099 *end = 0; |
2601 msg = msim_parse(g_strdup(session->rxbuf)); | 2100 msg = msim_parse(session->rxbuf); |
2602 if (!msg) { | 2101 if (!msg) { |
2603 purple_debug_info("msim", "msim_input_cb: couldn't parse rxbuf\n"); | 2102 purple_debug_info("msim", "msim_input_cb: couldn't parse rxbuf\n"); |
2604 purple_connection_error_reason (gc, | 2103 purple_connection_error_reason (gc, |
2605 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, | 2104 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, |
2606 _("Unparseable message")); | 2105 _("Unparseable message")); |
2614 msim_msg_free(msg); | 2113 msim_msg_free(msg); |
2615 } | 2114 } |
2616 | 2115 |
2617 /* Move remaining part of buffer to beginning. */ | 2116 /* Move remaining part of buffer to beginning. */ |
2618 session->rxoff -= strlen(session->rxbuf) + strlen(MSIM_FINAL_STRING); | 2117 session->rxoff -= strlen(session->rxbuf) + strlen(MSIM_FINAL_STRING); |
2619 memmove(session->rxbuf, end + strlen(MSIM_FINAL_STRING), | 2118 memmove(session->rxbuf, end + strlen(MSIM_FINAL_STRING), |
2620 session->rxsize - (end + strlen(MSIM_FINAL_STRING) - session->rxbuf)); | 2119 session->rxsize - (end + strlen(MSIM_FINAL_STRING) - session->rxbuf)); |
2621 | 2120 |
2622 /* Clear end of buffer | 2121 /* Clear end of buffer |
2623 * memset(end, 0, MSIM_READ_BUF_SIZE - (end - session->rxbuf)); | 2122 * memset(end, 0, MSIM_READ_BUF_SIZE - (end - session->rxbuf)); |
2624 */ | 2123 */ |
2625 } | 2124 } |
2626 } | |
2627 | |
2628 /* Setup a callback, to be called when a reply is received with the returned rid. | |
2629 * | |
2630 * @param cb The callback, an MSIM_USER_LOOKUP_CB. | |
2631 * @param data Arbitrary user data to be passed to callback (probably an MsimMessage *). | |
2632 * | |
2633 * @return The request/reply ID, used to link replies with requests, or -1. | |
2634 * Put the rid in your request, 'rid' field. | |
2635 * | |
2636 * TODO: Make more generic and more specific: | |
2637 * 1) MSIM_USER_LOOKUP_CB - make it for PERSIST_REPLY, not just user lookup | |
2638 * 2) data - make it an MsimMessage? | |
2639 */ | |
2640 guint | |
2641 msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb, | |
2642 gpointer data) | |
2643 { | |
2644 guint rid; | |
2645 | |
2646 g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); | |
2647 | |
2648 rid = session->next_rid++; | |
2649 | |
2650 g_hash_table_insert(session->user_lookup_cb, GUINT_TO_POINTER(rid), cb); | |
2651 g_hash_table_insert(session->user_lookup_cb_data, GUINT_TO_POINTER(rid), data); | |
2652 | |
2653 return rid; | |
2654 } | 2125 } |
2655 | 2126 |
2656 /** | 2127 /** |
2657 * Callback when connected. Sets up input handlers. | 2128 * Callback when connected. Sets up input handlers. |
2658 * | 2129 * |
2659 * @param data A PurpleConnection pointer. | 2130 * @param data A PurpleConnection pointer. |
2660 * @param source File descriptor. | 2131 * @param source File descriptor. |
2661 * @param error_message | 2132 * @param error_message |
2662 */ | 2133 */ |
2663 static void | 2134 static void |
2664 msim_connect_cb(gpointer data, gint source, const gchar *error_message) | 2135 msim_connect_cb(gpointer data, gint source, const gchar *error_message) |
2665 { | 2136 { |
2666 PurpleConnection *gc; | 2137 PurpleConnection *gc; |
2667 MsimSession *session; | 2138 MsimSession *session; |
2668 | 2139 |
2672 session = (MsimSession *)gc->proto_data; | 2143 session = (MsimSession *)gc->proto_data; |
2673 | 2144 |
2674 if (source < 0) { | 2145 if (source < 0) { |
2675 purple_connection_error_reason (gc, | 2146 purple_connection_error_reason (gc, |
2676 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, | 2147 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, |
2677 g_strdup_printf(_("Couldn't connect to host: %s (%d)"), | 2148 g_strdup_printf(_("Couldn't connect to host: %s (%d)"), |
2678 error_message ? error_message : "no message given", | 2149 error_message ? error_message : "no message given", |
2679 source)); | 2150 source)); |
2680 return; | 2151 return; |
2681 } | 2152 } |
2682 | 2153 |
2683 session->fd = source; | 2154 session->fd = source; |
2684 | 2155 |
2685 gc->inpa = purple_input_add(source, PURPLE_INPUT_READ, msim_input_cb, gc); | 2156 gc->inpa = purple_input_add(source, PURPLE_INPUT_READ, msim_input_cb, gc); |
2686 } | 2157 } |
2687 | 2158 |
2688 | 2159 /** |
2689 /** | 2160 * Start logging in to the MSIM servers. |
2161 * | |
2162 * @param acct Account information to use to login. | |
2163 */ | |
2164 static void | |
2165 msim_login(PurpleAccount *acct) | |
2166 { | |
2167 PurpleConnection *gc; | |
2168 const gchar *host; | |
2169 int port; | |
2170 | |
2171 g_return_if_fail(acct != NULL); | |
2172 g_return_if_fail(acct->username != NULL); | |
2173 | |
2174 purple_debug_info("msim", "logging in %s\n", acct->username); | |
2175 | |
2176 gc = purple_account_get_connection(acct); | |
2177 gc->proto_data = msim_session_new(acct); | |
2178 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_NO_URLDESC; | |
2179 | |
2180 /* | |
2181 * Lets wipe out our local list of blocked buddies. We'll get a | |
2182 * list of all blocked buddies from the server, and we shouldn't | |
2183 * have stuff in the local list that isn't on the server list. | |
2184 */ | |
2185 while (acct->deny != NULL) | |
2186 purple_privacy_deny_remove(acct, acct->deny->data, TRUE); | |
2187 | |
2188 /* 1. connect to server */ | |
2189 purple_connection_update_progress(gc, _("Connecting"), | |
2190 0, /* which connection step this is */ | |
2191 4); /* total number of steps */ | |
2192 | |
2193 host = purple_account_get_string(acct, "server", MSIM_SERVER); | |
2194 port = purple_account_get_int(acct, "port", MSIM_PORT); | |
2195 | |
2196 /* From purple.sf.net/api: | |
2197 * """Note that this function name can be misleading--although it is called | |
2198 * "proxy connect," it is used for establishing any outgoing TCP connection, | |
2199 * whether through a proxy or not.""" */ | |
2200 | |
2201 /* Calls msim_connect_cb when connected. */ | |
2202 if (!purple_proxy_connect(gc, acct, host, port, msim_connect_cb, gc)) { | |
2203 /* TODO: try other ports if in auto mode, then save | |
2204 * working port and try that first next time. */ | |
2205 purple_connection_error_reason (gc, | |
2206 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, | |
2207 _("Couldn't create socket")); | |
2208 return; | |
2209 } | |
2210 } | |
2211 | |
2212 /** | |
2690 * Close the connection. | 2213 * Close the connection. |
2691 * | 2214 * |
2692 * @param gc The connection. | 2215 * @param gc The connection. |
2693 */ | 2216 */ |
2694 void | 2217 static void |
2695 msim_close(PurpleConnection *gc) | 2218 msim_close(PurpleConnection *gc) |
2696 { | 2219 { |
2697 MsimSession *session; | 2220 MsimSession *session; |
2698 | 2221 |
2699 if (gc == NULL) { | 2222 if (gc == NULL) { |
2711 } | 2234 } |
2712 | 2235 |
2713 if (session->gc->inpa) { | 2236 if (session->gc->inpa) { |
2714 purple_input_remove(session->gc->inpa); | 2237 purple_input_remove(session->gc->inpa); |
2715 } | 2238 } |
2239 if (session->fd >= 0) { | |
2240 close(session->fd); | |
2241 session->fd = -1; | |
2242 } | |
2716 | 2243 |
2717 msim_session_destroy(session); | 2244 msim_session_destroy(session); |
2718 } | 2245 } |
2719 | 2246 |
2720 | 2247 /** |
2721 /** | 2248 * Schedule an IM to be sent once the user ID is looked up. |
2722 * Obtain the status text for a buddy. | 2249 * |
2723 * | 2250 * @param gc Connection. |
2724 * @param buddy The buddy to obtain status text for. | 2251 * @param who A user id, email, or username to send the message to. |
2725 * | 2252 * @param message Instant message text to send. |
2726 * @return Status text, or NULL if error. Caller g_free()'s. | 2253 * @param flags Flags. |
2727 * | 2254 * |
2728 */ | 2255 * @return 1 if successful or postponed, -1 if failed |
2729 char * | 2256 * |
2730 msim_status_text(PurpleBuddy *buddy) | 2257 * Allows sending to a user by username, email address, or userid. If |
2258 * a username or email address is given, the userid must be looked up. | |
2259 * This function does that by calling msim_postprocess_outgoing(). | |
2260 */ | |
2261 static int | |
2262 msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, | |
2263 PurpleMessageFlags flags) | |
2264 { | |
2265 MsimSession *session; | |
2266 gchar *message_msim; | |
2267 int rc; | |
2268 | |
2269 g_return_val_if_fail(gc != NULL, -1); | |
2270 g_return_val_if_fail(who != NULL, -1); | |
2271 g_return_val_if_fail(message != NULL, -1); | |
2272 | |
2273 /* 'flags' has many options, not used here. */ | |
2274 | |
2275 session = (MsimSession *)gc->proto_data; | |
2276 | |
2277 g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); | |
2278 | |
2279 message_msim = html_to_msim_markup(session, message); | |
2280 | |
2281 if (msim_send_bm(session, who, message_msim, MSIM_BM_INSTANT)) { | |
2282 /* Return 1 to have Purple show this IM as being sent, 0 to not. I always | |
2283 * return 1 even if the message could not be sent, since I don't know if | |
2284 * it has failed yet--because the IM is only sent after the userid is | |
2285 * retrieved from the server (which happens after this function returns). | |
2286 * If an error does occur, it should be logged to the IM window. | |
2287 */ | |
2288 rc = 1; | |
2289 } else { | |
2290 rc = -1; | |
2291 } | |
2292 | |
2293 g_free(message_msim); | |
2294 | |
2295 return rc; | |
2296 } | |
2297 | |
2298 /** | |
2299 * Handle when our user starts or stops typing to another user. | |
2300 * | |
2301 * @param gc | |
2302 * @param name The buddy name to which our user is typing to | |
2303 * @param state PURPLE_TYPING, PURPLE_TYPED, PURPLE_NOT_TYPING | |
2304 * | |
2305 * @return 0 | |
2306 */ | |
2307 static unsigned int | |
2308 msim_send_typing(PurpleConnection *gc, const gchar *name, | |
2309 PurpleTypingState state) | |
2310 { | |
2311 const gchar *typing_str; | |
2312 MsimSession *session; | |
2313 | |
2314 g_return_val_if_fail(gc != NULL, 0); | |
2315 g_return_val_if_fail(name != NULL, 0); | |
2316 | |
2317 session = (MsimSession *)gc->proto_data; | |
2318 | |
2319 g_return_val_if_fail(MSIM_SESSION_VALID(session), 0); | |
2320 | |
2321 switch (state) { | |
2322 case PURPLE_TYPING: | |
2323 typing_str = "%typing%"; | |
2324 break; | |
2325 | |
2326 case PURPLE_TYPED: | |
2327 case PURPLE_NOT_TYPING: | |
2328 default: | |
2329 typing_str = "%stoptyping%"; | |
2330 break; | |
2331 } | |
2332 | |
2333 purple_debug_info("msim", "msim_send_typing(%s): %d (%s)\n", name, state, typing_str); | |
2334 msim_send_bm(session, name, typing_str, MSIM_BM_ACTION); | |
2335 return 0; | |
2336 } | |
2337 | |
2338 /** | |
2339 * Callback for msim_get_info(), for when user info is received. | |
2340 */ | |
2341 static void | |
2342 msim_get_info_cb(MsimSession *session, const MsimMessage *user_info_msg, | |
2343 gpointer data) | |
2344 { | |
2345 MsimMessage *msg; | |
2346 gchar *username; | |
2347 PurpleNotifyUserInfo *user_info; | |
2348 MsimUser *user; | |
2349 | |
2350 g_return_if_fail(MSIM_SESSION_VALID(session)); | |
2351 | |
2352 /* Get user{name,id} from msim_get_info, passed as an MsimMessage for | |
2353 orthogonality. */ | |
2354 msg = (MsimMessage *)data; | |
2355 g_return_if_fail(msg != NULL); | |
2356 | |
2357 username = msim_msg_get_string(msg, "user"); | |
2358 if (!username) { | |
2359 purple_debug_info("msim", "msim_get_info_cb: no 'user' in msg\n"); | |
2360 return; | |
2361 } | |
2362 | |
2363 msim_msg_free(msg); | |
2364 purple_debug_info("msim", "msim_get_info_cb: got for user: %s\n", username); | |
2365 | |
2366 user = msim_find_user(session, username); | |
2367 | |
2368 if (!user) { | |
2369 /* User isn't on blist, create a temporary user to store info. */ | |
2370 user = g_new0(MsimUser, 1); | |
2371 user->temporary_user = TRUE; | |
2372 } | |
2373 | |
2374 /* Update user structure with new information */ | |
2375 msim_store_user_info(session, user_info_msg, user); | |
2376 | |
2377 user_info = purple_notify_user_info_new(); | |
2378 | |
2379 /* Append data from MsimUser to PurpleNotifyUserInfo for display, full */ | |
2380 msim_append_user_info(session, user_info, user, TRUE); | |
2381 | |
2382 purple_notify_userinfo(session->gc, username, user_info, NULL, NULL); | |
2383 purple_debug_info("msim", "msim_get_info_cb: username=%s\n", username); | |
2384 | |
2385 purple_notify_user_info_destroy(user_info); | |
2386 | |
2387 if (user->temporary_user) { | |
2388 g_free(user->client_info); | |
2389 g_free(user->gender); | |
2390 g_free(user->location); | |
2391 g_free(user->headline); | |
2392 g_free(user->display_name); | |
2393 g_free(user->username); | |
2394 g_free(user->image_url); | |
2395 g_free(user); | |
2396 } | |
2397 g_free(username); | |
2398 } | |
2399 | |
2400 /** | |
2401 * Retrieve a user's profile. | |
2402 * @param username Username, user ID, or email address to lookup. | |
2403 */ | |
2404 static void | |
2405 msim_get_info(PurpleConnection *gc, const gchar *username) | |
2731 { | 2406 { |
2732 MsimSession *session; | 2407 MsimSession *session; |
2733 MsimUser *user; | 2408 MsimUser *user; |
2734 const gchar *display_name, *headline; | 2409 gchar *user_to_lookup; |
2735 | 2410 MsimMessage *user_msg; |
2736 g_return_val_if_fail(buddy != NULL, NULL); | 2411 |
2737 | 2412 g_return_if_fail(gc != NULL); |
2738 user = msim_get_user_from_buddy(buddy); | 2413 g_return_if_fail(username != NULL); |
2739 | 2414 |
2740 session = (MsimSession *)buddy->account->gc->proto_data; | 2415 session = (MsimSession *)gc->proto_data; |
2741 g_return_val_if_fail(MSIM_SESSION_VALID(session), NULL); | 2416 |
2742 | 2417 g_return_if_fail(MSIM_SESSION_VALID(session)); |
2743 display_name = headline = NULL; | 2418 |
2744 | 2419 /* Obtain uid of buddy. */ |
2745 /* Retrieve display name and/or headline, depending on user preference. */ | 2420 user = msim_find_user(session, username); |
2746 if (purple_account_get_bool(session->account, "show_headline", TRUE)) { | 2421 |
2747 headline = user->headline; | 2422 /* If is on buddy list, lookup by uid since it is faster. */ |
2748 } | 2423 if (user && user->id) { |
2749 | 2424 user_to_lookup = g_strdup_printf("%d", user->id); |
2750 if (purple_account_get_bool(session->account, "show_display_name", FALSE)) { | 2425 } else { |
2751 display_name = user->display_name; | 2426 /* Looking up buddy not on blist. Lookup by whatever user entered. */ |
2752 } | 2427 user_to_lookup = g_strdup(username); |
2753 | 2428 } |
2754 /* Return appropriate combination of display name and/or headline, or neither. */ | 2429 |
2755 | 2430 /* Pass the username to msim_get_info_cb(), because since we lookup |
2756 if (display_name && headline) { | 2431 * by userid, the userinfo message will only contain the uid (not |
2757 return g_strconcat(display_name, " ", headline, NULL); | 2432 * the username) but it would be useful to display the username too. |
2758 } else if (display_name) { | 2433 */ |
2759 return g_strdup(display_name); | 2434 user_msg = msim_msg_new( |
2760 } else if (headline) { | 2435 "user", MSIM_TYPE_STRING, g_strdup(username), |
2761 return g_strdup(headline); | 2436 NULL); |
2762 } | 2437 purple_debug_info("msim", "msim_get_info, setting up lookup, user=%s\n", username); |
2763 | 2438 |
2764 return NULL; | 2439 msim_lookup_user(session, user_to_lookup, msim_get_info_cb, user_msg); |
2765 } | 2440 |
2766 | 2441 g_free(user_to_lookup); |
2767 /** | 2442 } |
2768 * Obtain the tooltip text for a buddy. | 2443 |
2769 * | 2444 /** |
2770 * @param buddy Buddy to obtain tooltip text on. | 2445 * Set status using an MSIM_STATUS_CODE_* value. |
2771 * @param user_info Variable modified to have the tooltip text. | 2446 * @param status_code An MSIM_STATUS_CODE_* value. |
2772 * @param full TRUE if should obtain full tooltip text. | 2447 * @param statstring Status string, must be a dynamic string (will be freed by msim_send). |
2773 * | 2448 */ |
2774 */ | 2449 static void |
2775 void | 2450 msim_set_status_code(MsimSession *session, guint status_code, gchar *statstring) |
2776 msim_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, | 2451 { |
2777 gboolean full) | 2452 g_return_if_fail(MSIM_SESSION_VALID(session)); |
2778 { | 2453 g_return_if_fail(statstring != NULL); |
2779 MsimUser *user; | 2454 |
2780 | 2455 purple_debug_info("msim", "msim_set_status_code: going to set status to code=%d,str=%s\n", |
2781 g_return_if_fail(buddy != NULL); | 2456 status_code, statstring); |
2782 g_return_if_fail(user_info != NULL); | 2457 |
2783 | 2458 if (!msim_send(session, |
2784 user = msim_get_user_from_buddy(buddy); | 2459 "status", MSIM_TYPE_INTEGER, status_code, |
2785 | 2460 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, |
2786 if (PURPLE_BUDDY_IS_ONLINE(buddy)) { | 2461 "statstring", MSIM_TYPE_STRING, statstring, |
2787 MsimSession *session; | 2462 "locstring", MSIM_TYPE_STRING, g_strdup(""), |
2788 | 2463 NULL)) |
2789 session = (MsimSession *)buddy->account->gc->proto_data; | 2464 { |
2790 | 2465 purple_debug_info("msim", "msim_set_status: failed to set status\n"); |
2791 g_return_if_fail(MSIM_SESSION_VALID(session)); | 2466 } |
2792 | 2467 } |
2793 /* TODO: if (full), do something different? */ | 2468 |
2794 | 2469 /** |
2795 /* TODO: request information? have to figure out how to do | 2470 * Set your status - callback for when user manually sets it. |
2796 * the asynchronous lookup like oscar does (tooltip shows | 2471 */ |
2797 * 'retrieving...' if not yet available, then changes when it is). | 2472 static void |
2798 * | 2473 msim_set_status(PurpleAccount *account, PurpleStatus *status) |
2799 * Right now, only show what we have on hand. | 2474 { |
2475 PurpleStatusType *type; | |
2476 PurplePresence *pres; | |
2477 MsimSession *session; | |
2478 guint status_code; | |
2479 const gchar *message; | |
2480 gchar *stripped; | |
2481 gchar *unrecognized_msg; | |
2482 | |
2483 session = (MsimSession *)account->gc->proto_data; | |
2484 | |
2485 g_return_if_fail(MSIM_SESSION_VALID(session)); | |
2486 | |
2487 type = purple_status_get_type(status); | |
2488 pres = purple_status_get_presence(status); | |
2489 | |
2490 switch (purple_status_type_get_primitive(type)) { | |
2491 case PURPLE_STATUS_AVAILABLE: | |
2492 purple_debug_info("msim", "msim_set_status: available (%d->%d)\n", PURPLE_STATUS_AVAILABLE, | |
2493 MSIM_STATUS_CODE_ONLINE); | |
2494 status_code = MSIM_STATUS_CODE_ONLINE; | |
2495 break; | |
2496 | |
2497 case PURPLE_STATUS_INVISIBLE: | |
2498 purple_debug_info("msim", "msim_set_status: invisible (%d->%d)\n", PURPLE_STATUS_INVISIBLE, | |
2499 MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN); | |
2500 status_code = MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN; | |
2501 break; | |
2502 | |
2503 case PURPLE_STATUS_AWAY: | |
2504 purple_debug_info("msim", "msim_set_status: away (%d->%d)\n", PURPLE_STATUS_AWAY, | |
2505 MSIM_STATUS_CODE_AWAY); | |
2506 status_code = MSIM_STATUS_CODE_AWAY; | |
2507 break; | |
2508 | |
2509 default: | |
2510 purple_debug_info("msim", "msim_set_status: unknown " | |
2511 "status interpreting as online"); | |
2512 status_code = MSIM_STATUS_CODE_ONLINE; | |
2513 | |
2514 unrecognized_msg = g_strdup_printf("msim_set_status, unrecognized status type: %d\n", | |
2515 purple_status_type_get_primitive(type)); | |
2516 msim_unrecognized(session, NULL, unrecognized_msg); | |
2517 g_free(unrecognized_msg); | |
2518 | |
2519 break; | |
2520 } | |
2521 | |
2522 message = purple_status_get_attr_string(status, "message"); | |
2523 | |
2524 /* Status strings are plain text. */ | |
2525 if (message != NULL) | |
2526 stripped = purple_markup_strip_html(message); | |
2527 else | |
2528 stripped = g_strdup(""); | |
2529 | |
2530 msim_set_status_code(session, status_code, stripped); | |
2531 | |
2532 /* If we should be idle, set that status. Time is irrelevant here. */ | |
2533 if (purple_presence_is_idle(pres) && status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN) | |
2534 msim_set_idle(account->gc, 1); | |
2535 } | |
2536 | |
2537 /** | |
2538 * Go idle. | |
2539 */ | |
2540 static void | |
2541 msim_set_idle(PurpleConnection *gc, int time) | |
2542 { | |
2543 MsimSession *session; | |
2544 PurpleStatus *status; | |
2545 | |
2546 g_return_if_fail(gc != NULL); | |
2547 | |
2548 session = (MsimSession *)gc->proto_data; | |
2549 | |
2550 g_return_if_fail(MSIM_SESSION_VALID(session)); | |
2551 | |
2552 status = purple_account_get_active_status(session->account); | |
2553 | |
2554 if (time == 0) { | |
2555 /* Going back from idle. In msim, idle is mutually exclusive | |
2556 * from the other states (you can only be away or idle, but not | |
2557 * both, for example), so by going non-idle I go back to what | |
2558 * libpurple says I should be. | |
2800 */ | 2559 */ |
2801 | 2560 msim_set_status(session->account, status); |
2802 /* Show abbreviated user info. */ | 2561 } else { |
2803 msim_append_user_info(session, user_info, user, FALSE); | 2562 const gchar *message; |
2804 } | 2563 gchar *stripped; |
2805 } | 2564 |
2806 | 2565 /* Set the idle message to the status message from the real |
2807 /** Add contact from server to buddy list, after looking up username. | 2566 * current status. |
2808 * Callback from msim_add_contact_from_server(). | 2567 */ |
2809 * | 2568 message = purple_status_get_attr_string(status, "message"); |
2810 * @param data An MsimMessage * of the contact information. Will be freed. | 2569 if (message != NULL) |
2570 stripped = purple_markup_strip_html(message); | |
2571 else | |
2572 stripped = g_strdup(""); | |
2573 | |
2574 /* msim doesn't support idle time, so just go idle */ | |
2575 msim_set_status_code(session, MSIM_STATUS_CODE_IDLE, stripped); | |
2576 } | |
2577 } | |
2578 | |
2579 /** | |
2580 * @return TRUE if everything was ok, FALSE if something went awry. | |
2581 */ | |
2582 static gboolean | |
2583 msim_update_blocklist_for_buddy(MsimSession *session, const char *name, gboolean allow, gboolean block) | |
2584 { | |
2585 MsimMessage *msg; | |
2586 GList *list; | |
2587 | |
2588 list = NULL; | |
2589 list = g_list_prepend(list, allow ? "a+" : "a-"); | |
2590 list = g_list_prepend(list, "<uid>"); | |
2591 list = g_list_prepend(list, block ? "b+" : "b-"); | |
2592 list = g_list_prepend(list, "<uid>"); | |
2593 list = g_list_reverse(list); | |
2594 | |
2595 msg = msim_msg_new( | |
2596 "blocklist", MSIM_TYPE_BOOLEAN, TRUE, | |
2597 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
2598 /* TODO: MsimMessage lists. Currently <uid> isn't replaced in lists. */ | |
2599 /* "idlist", MSIM_TYPE_STRING, g_strdup("a-|<uid>|b-|<uid>"), */ | |
2600 "idlist", MSIM_TYPE_LIST, list, | |
2601 NULL); | |
2602 | |
2603 if (!msim_postprocess_outgoing(session, msg, name, "idlist", NULL)) { | |
2604 purple_debug_error("myspace", | |
2605 "blocklist command failed for %s, allow=%d, block=%d\n", | |
2606 name, allow, block); | |
2607 msim_msg_free(msg); | |
2608 return FALSE; | |
2609 } | |
2610 | |
2611 msim_msg_free(msg); | |
2612 | |
2613 return TRUE; | |
2614 } | |
2615 | |
2616 /** | |
2617 * Add a buddy to user's buddy list. | |
2811 */ | 2618 */ |
2812 static void | 2619 static void |
2813 msim_add_contact_from_server_cb(MsimSession *session, MsimMessage *user_lookup_info, gpointer data) | 2620 msim_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) |
2814 { | 2621 { |
2815 MsimMessage *contact_info, *user_lookup_info_body; | 2622 MsimSession *session; |
2816 PurpleGroup *group; | 2623 MsimMessage *msg; |
2817 PurpleBuddy *buddy; | 2624 MsimMessage *msg_persist; |
2818 MsimUser *user; | 2625 MsimMessage *body; |
2819 gchar *username, *group_name; | 2626 |
2820 guint uid; | 2627 session = (MsimSession *)gc->proto_data; |
2821 | 2628 purple_debug_info("msim", "msim_add_buddy: want to add %s to %s\n", |
2822 contact_info = (MsimMessage *)data; | 2629 buddy->name, (group && group->name) ? group->name : "(no group)"); |
2823 purple_debug_info("msim_add_contact_from_server_cb", "contact_info addr=%p\n", contact_info); | 2630 |
2824 uid = msim_msg_get_integer(contact_info, "ContactID"); | 2631 msg = msim_msg_new( |
2825 | 2632 "addbuddy", MSIM_TYPE_BOOLEAN, TRUE, |
2826 if (!user_lookup_info) { | 2633 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, |
2827 username = g_strdup(msim_uid2username_from_blist(session->account, uid)); | 2634 /* "newprofileid" will be inserted here with uid. */ |
2828 g_return_if_fail(username != NULL); | 2635 "reason", MSIM_TYPE_STRING, g_strdup(""), |
2829 } else { | 2636 NULL); |
2830 user_lookup_info_body = msim_msg_get_dictionary(user_lookup_info, "body"); | 2637 |
2831 username = msim_msg_get_string(user_lookup_info_body, "UserName"); | 2638 if (!msim_postprocess_outgoing(session, msg, buddy->name, "newprofileid", "reason")) { |
2832 msim_msg_free(user_lookup_info_body); | 2639 purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("'addbuddy' command failed.")); |
2833 g_return_if_fail(username != NULL); | 2640 msim_msg_free(msg); |
2834 } | 2641 return; |
2835 | 2642 } |
2836 purple_debug_info("msim_add_contact_from_server_cb", | 2643 msim_msg_free(msg); |
2837 "*** about to add/update username=%s\n", username); | 2644 |
2838 | 2645 /* TODO: if addbuddy fails ('error' message is returned), delete added buddy from |
2839 /* 1. Creates a new group, or gets existing group if it exists (or so | 2646 * buddy list since Purple adds it locally. */ |
2840 * the documentation claims). */ | 2647 |
2841 group_name = msim_msg_get_string(contact_info, "GroupName"); | 2648 body = msim_msg_new( |
2842 if (!group_name || (*group_name == '\0')) { | 2649 "ContactID", MSIM_TYPE_STRING, g_strdup("<uid>"), |
2843 g_free(group_name); | 2650 "GroupName", MSIM_TYPE_STRING, g_strdup(group->name), |
2844 group_name = g_strdup(_("IM Friends")); | 2651 "Position", MSIM_TYPE_INTEGER, 1000, |
2845 purple_debug_info("myspace", "No GroupName specified, defaulting to '%s'.\n", group_name); | 2652 "Visibility", MSIM_TYPE_INTEGER, 1, |
2846 } | 2653 "NickName", MSIM_TYPE_STRING, g_strdup(""), |
2847 group = purple_find_group(group_name); | 2654 "NameSelect", MSIM_TYPE_INTEGER, 0, |
2848 if (!group) { | 2655 NULL); |
2849 group = purple_group_new(group_name); | 2656 |
2850 /* Add group to beginning. See #2752. */ | 2657 /* TODO: Update blocklist. */ |
2851 purple_blist_add_group(group, NULL); | 2658 |
2852 } | 2659 msg_persist = msim_msg_new( |
2853 g_free(group_name); | 2660 "persist", MSIM_TYPE_INTEGER, 1, |
2854 | 2661 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, |
2855 /* 2. Get or create buddy */ | 2662 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_PUT, |
2856 buddy = purple_find_buddy(session->account, username); | 2663 "dsn", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_DSN, |
2857 if (!buddy) { | 2664 "uid", MSIM_TYPE_INTEGER, session->userid, |
2858 purple_debug_info("msim_add_contact_from_server_cb", | 2665 "lid", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_LID, |
2859 "creating new buddy: %s\n", username); | 2666 /* TODO: Use msim_new_reply_callback to get rid. */ |
2860 buddy = purple_buddy_new(session->account, username, NULL); | 2667 "rid", MSIM_TYPE_INTEGER, session->next_rid++, |
2861 } | 2668 "body", MSIM_TYPE_DICTIONARY, body, |
2862 | 2669 NULL); |
2863 /* TODO: use 'Position' in contact_info to take into account where buddy is */ | 2670 |
2864 purple_blist_add_buddy(buddy, NULL, group, NULL /* insertion point */); | 2671 if (!msim_postprocess_outgoing(session, msg_persist, buddy->name, "body", NULL)) |
2865 | 2672 { |
2866 /* 3. Update buddy information */ | 2673 purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("persist command failed")); |
2867 user = msim_get_user_from_buddy(buddy); | 2674 msim_msg_free(msg_persist); |
2868 | 2675 return; |
2869 user->id = uid; | 2676 } |
2870 /* Keep track of the user ID across sessions */ | 2677 msim_msg_free(msg_persist); |
2871 purple_blist_node_set_int(&buddy->node, "UserID", uid); | 2678 |
2872 | 2679 /* Add to allow list, remove from block list */ |
2873 /* Stores a few fields in the MsimUser, relevant to the buddy itself. | 2680 msim_update_blocklist_for_buddy(session, buddy->name, TRUE, FALSE); |
2874 * AvatarURL, Headline, ContactID. */ | 2681 } |
2875 msim_store_user_info(session, contact_info, NULL); | 2682 |
2876 | 2683 /** |
2877 /* TODO: other fields, store in 'user' */ | 2684 * Remove a buddy from the user's buddy list. |
2878 msim_msg_free(contact_info); | 2685 */ |
2879 | |
2880 g_free(username); | |
2881 } | |
2882 | |
2883 /** Add first ContactID in contact_info to buddy's list. Used to add | |
2884 * server-side buddies to client-side list. | |
2885 * | |
2886 * @return TRUE if added. | |
2887 * */ | |
2888 static gboolean | |
2889 msim_add_contact_from_server(MsimSession *session, MsimMessage *contact_info) | |
2890 { | |
2891 guint uid; | |
2892 const gchar *username; | |
2893 | |
2894 uid = msim_msg_get_integer(contact_info, "ContactID"); | |
2895 g_return_val_if_fail(uid != 0, FALSE); | |
2896 | |
2897 /* Lookup the username, since NickName and IMName is unreliable */ | |
2898 username = msim_uid2username_from_blist(session->account, uid); | |
2899 if (!username) { | |
2900 gchar *uid_str; | |
2901 | |
2902 uid_str = g_strdup_printf("%d", uid); | |
2903 purple_debug_info("msim_add_contact_from_server", | |
2904 "contact_info addr=%p\n", contact_info); | |
2905 msim_lookup_user(session, uid_str, msim_add_contact_from_server_cb, (gpointer)msim_msg_clone(contact_info)); | |
2906 g_free(uid_str); | |
2907 } else { | |
2908 msim_add_contact_from_server_cb(session, NULL, (gpointer)msim_msg_clone(contact_info)); | |
2909 } | |
2910 | |
2911 /* Say that the contact was added, even if we're still looking up | |
2912 * their username. */ | |
2913 return TRUE; | |
2914 } | |
2915 | |
2916 /** Called when contact list is received from server. */ | |
2917 static void | 2686 static void |
2918 msim_got_contact_list(MsimSession *session, MsimMessage *reply, gpointer user_data) | 2687 msim_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) |
2919 { | 2688 { |
2920 MsimMessage *body, *body_node; | 2689 MsimSession *session; |
2921 gchar *msg; | 2690 MsimMessage *delbuddy_msg; |
2922 guint buddy_count; | 2691 MsimMessage *persist_msg; |
2923 | 2692 |
2924 msim_msg_dump("msim_got_contact_list: reply=%s", reply); | 2693 session = (MsimSession *)gc->proto_data; |
2925 | 2694 |
2926 body = msim_msg_get_dictionary(reply, "body"); | 2695 delbuddy_msg = msim_msg_new( |
2927 if (!body) { | 2696 "delbuddy", MSIM_TYPE_BOOLEAN, TRUE, |
2928 /* No friends. Not an error. */ | 2697 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, |
2698 /* 'delprofileid' with uid will be inserted here. */ | |
2699 NULL); | |
2700 | |
2701 if (!msim_postprocess_outgoing(session, delbuddy_msg, buddy->name, "delprofileid", NULL)) { | |
2702 purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("'delbuddy' command failed")); | |
2703 msim_msg_free(delbuddy_msg); | |
2929 return; | 2704 return; |
2930 } | 2705 } |
2931 | 2706 msim_msg_free(delbuddy_msg); |
2932 buddy_count = 0; | 2707 |
2933 | 2708 persist_msg = msim_msg_new( |
2934 for (body_node = body; | |
2935 body_node != NULL; | |
2936 body_node = msim_msg_get_next_element_node(body_node)) | |
2937 { | |
2938 MsimMessageElement *elem; | |
2939 | |
2940 elem = (MsimMessageElement *)body_node->data; | |
2941 | |
2942 if (g_str_equal(elem->name, "ContactID")) | |
2943 { | |
2944 /* Will look for first contact in body_node */ | |
2945 if (msim_add_contact_from_server(session, body_node)) { | |
2946 ++buddy_count; | |
2947 } | |
2948 } | |
2949 } | |
2950 | |
2951 switch (GPOINTER_TO_UINT(user_data)) { | |
2952 case MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS: | |
2953 msg = g_strdup_printf(ngettext("%d buddy was added or updated from the server (including buddies already on the server-side list)", | |
2954 "%d buddies were added or updated from the server (including buddies already on the server-side list)", | |
2955 buddy_count), | |
2956 buddy_count); | |
2957 purple_notify_info(session->account, _("Add contacts from server"), msg, NULL); | |
2958 g_free(msg); | |
2959 break; | |
2960 | |
2961 case MSIM_CONTACT_LIST_IMPORT_TOP_FRIENDS: | |
2962 /* TODO */ | |
2963 break; | |
2964 | |
2965 case MSIM_CONTACT_LIST_INITIAL_FRIENDS: | |
2966 /* Nothing */ | |
2967 break; | |
2968 } | |
2969 | |
2970 msim_msg_free(body); | |
2971 } | |
2972 | |
2973 /* Get contact list, calling msim_got_contact_list() with what_to_do_after as user_data gpointer. */ | |
2974 static gboolean | |
2975 msim_get_contact_list(MsimSession *session, int what_to_do_after) | |
2976 { | |
2977 return msim_send(session, | |
2978 "persist", MSIM_TYPE_INTEGER, 1, | 2709 "persist", MSIM_TYPE_INTEGER, 1, |
2979 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | 2710 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, |
2980 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET, | 2711 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_DELETE, |
2981 "dsn", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_DSN, | 2712 "dsn", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_DSN, |
2982 "lid", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_LID, | 2713 "lid", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_LID, |
2983 "uid", MSIM_TYPE_INTEGER, session->userid, | 2714 "uid", MSIM_TYPE_INTEGER, session->userid, |
2984 "rid", MSIM_TYPE_INTEGER, | 2715 "rid", MSIM_TYPE_INTEGER, session->next_rid++, |
2985 msim_new_reply_callback(session, msim_got_contact_list, GUINT_TO_POINTER(what_to_do_after)), | 2716 /* <uid> will be replaced by postprocessing */ |
2986 "body", MSIM_TYPE_STRING, g_strdup(""), | 2717 "body", MSIM_TYPE_STRING, g_strdup("ContactID=<uid>"), |
2987 NULL); | 2718 NULL); |
2988 } | 2719 |
2989 | 2720 if (!msim_postprocess_outgoing(session, persist_msg, buddy->name, "body", NULL)) { |
2990 | 2721 purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("persist command failed")); |
2991 /** Called when friends have been imported to buddy list on server. */ | 2722 msim_msg_free(persist_msg); |
2992 static void | |
2993 msim_import_friends_cb(MsimSession *session, MsimMessage *reply, gpointer user_data) | |
2994 { | |
2995 MsimMessage *body; | |
2996 gchar *completed; | |
2997 msim_msg_dump("msim_import_friends_cb=%s", reply); | |
2998 | |
2999 /* Check if the friends were imported successfully. */ | |
3000 body = msim_msg_get_dictionary(reply, "body"); | |
3001 g_return_if_fail(body != NULL); | |
3002 completed = msim_msg_get_string(body, "Completed"); | |
3003 g_return_if_fail(body != NULL); | |
3004 msim_msg_free(body); | |
3005 if (!g_str_equal(completed, "True")) | |
3006 { | |
3007 purple_debug_info("msim_import_friends_cb", | |
3008 "failed to import friends: %s", completed); | |
3009 purple_notify_error(session->account, _("Add friends from MySpace.com"), | |
3010 _("Importing friends failed"), NULL); | |
3011 g_free(completed); | |
3012 return; | 2723 return; |
3013 } | 2724 } |
3014 g_free(completed); | 2725 msim_msg_free(persist_msg); |
3015 | 2726 |
3016 purple_debug_info("msim_import_friends_cb", | 2727 /* |
3017 "added friends to server-side buddy list, requesting new contacts from server"); | 2728 * Remove from our approve list and from our block list (this |
3018 | 2729 * doesn't seem like it would be necessary, but the official client |
3019 msim_get_contact_list(session, MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS); | 2730 * does it) |
3020 | 2731 */ |
3021 /* TODO: show, X friends have been added */ | 2732 if (!msim_update_blocklist_for_buddy(session, buddy->name, FALSE, FALSE)) { |
3022 } | 2733 purple_notify_error(NULL, NULL, |
3023 | 2734 _("Failed to remove buddy"), _("blocklist command failed")); |
3024 /** Import friends from myspace.com. */ | 2735 return; |
3025 static void msim_import_friends(PurplePluginAction *action) | 2736 } |
3026 { | 2737 if (buddy->proto_data) { |
3027 PurpleConnection *gc; | 2738 msim_user_free(buddy->proto_data); |
2739 buddy->proto_data = NULL; | |
2740 } | |
2741 } | |
2742 | |
2743 /** | |
2744 * Remove a buddy from the user's buddy list and add them to the block list. | |
2745 */ | |
2746 static void | |
2747 msim_add_deny(PurpleConnection *gc, const char *name) | |
2748 { | |
3028 MsimSession *session; | 2749 MsimSession *session; |
3029 gchar *group_name; | 2750 MsimMessage *msg, *body; |
3030 | 2751 |
3031 gc = (PurpleConnection *)action->context; | |
3032 session = (MsimSession *)gc->proto_data; | 2752 session = (MsimSession *)gc->proto_data; |
3033 | 2753 |
3034 group_name = "MySpace Friends"; | 2754 /* Remove from buddy list */ |
3035 | 2755 msg = msim_msg_new( |
3036 g_return_if_fail(msim_send(session, | 2756 "delbuddy", MSIM_TYPE_BOOLEAN, TRUE, |
2757 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
2758 /* 'delprofileid' with uid will be inserted here. */ | |
2759 NULL); | |
2760 if (!msim_postprocess_outgoing(session, msg, name, "delprofileid", NULL)) | |
2761 purple_debug_error("myspace", "delbuddy command failed\n"); | |
2762 msim_msg_free(msg); | |
2763 | |
2764 /* Remove from our approve list and add to our block list */ | |
2765 msim_update_blocklist_for_buddy(session, name, FALSE, TRUE); | |
2766 | |
2767 /* | |
2768 * Add the buddy to our list of blocked contacts, so we know they | |
2769 * are blocked if we log in with another client | |
2770 */ | |
2771 body = msim_msg_new( | |
2772 "ContactID", MSIM_TYPE_STRING, g_strdup("<uid>"), | |
2773 "Visibility", MSIM_TYPE_INTEGER, 2, | |
2774 NULL); | |
2775 msg = msim_msg_new( | |
3037 "persist", MSIM_TYPE_INTEGER, 1, | 2776 "persist", MSIM_TYPE_INTEGER, 1, |
3038 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | 2777 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, |
3039 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_PUT, | 2778 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_PUT, |
3040 "dsn", MSIM_TYPE_INTEGER, MC_IMPORT_ALL_FRIENDS_DSN, | 2779 "dsn", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_DSN, |
3041 "lid", MSIM_TYPE_INTEGER, MC_IMPORT_ALL_FRIENDS_LID, | 2780 "lid", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_LID, |
3042 "uid", MSIM_TYPE_INTEGER, session->userid, | 2781 "rid", MSIM_TYPE_INTEGER, session->next_rid++, |
3043 "rid", MSIM_TYPE_INTEGER, | 2782 "body", MSIM_TYPE_DICTIONARY, body, |
3044 msim_new_reply_callback(session, msim_import_friends_cb, NULL), | 2783 NULL); |
3045 "body", MSIM_TYPE_STRING, | 2784 if (!msim_postprocess_outgoing(session, msg, name, "body", NULL)) |
3046 g_strdup_printf("GroupName=%s", group_name), | 2785 purple_debug_error("myspace", "add to block list command failed\n"); |
3047 NULL)); | 2786 msim_msg_free(msg); |
3048 | 2787 |
3049 | 2788 /* |
3050 } | 2789 * TODO: MySpace doesn't allow blocked buddies on our buddy list, |
3051 | 2790 * do they? If not then we need to remove the buddy from |
3052 /** Actions menu for account. */ | 2791 * libpurple's buddy list. |
3053 GList * | 2792 */ |
3054 msim_actions(PurplePlugin *plugin, gpointer context) | 2793 } |
3055 { | 2794 |
3056 PurpleConnection *gc; | 2795 /** |
3057 GList *menu; | 2796 * Remove a buddy from the user's block list. |
3058 PurplePluginAction *act; | 2797 */ |
3059 | 2798 static void |
3060 gc = (PurpleConnection *)context; | 2799 msim_rem_deny(PurpleConnection *gc, const char *name) |
3061 | 2800 { |
3062 menu = NULL; | 2801 MsimSession *session; |
3063 | 2802 MsimMessage *msg, *body; |
3064 #if 0 | 2803 |
3065 /* TODO: find out how */ | 2804 session = (MsimSession *)gc->proto_data; |
3066 act = purple_plugin_action_new(_("Find people..."), msim_); | 2805 |
3067 menu = g_list_append(menu, act); | 2806 /* |
3068 | 2807 * Remove from our list of blocked contacts, so we know they |
3069 act = purple_plugin_action_new(_("Change IM name..."), NULL); | 2808 * are no longer blocked if we log in with another client |
3070 menu = g_list_append(menu, act); | 2809 */ |
3071 #endif | 2810 body = msim_msg_new( |
3072 | 2811 "ContactID", MSIM_TYPE_STRING, g_strdup("<uid>"), |
3073 act = purple_plugin_action_new(_("Add friends from MySpace.com"), msim_import_friends); | 2812 NULL); |
3074 menu = g_list_append(menu, act); | 2813 msg = msim_msg_new( |
3075 | 2814 "persist", MSIM_TYPE_INTEGER, 1, |
3076 return menu; | 2815 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, |
3077 } | 2816 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_DELETE, |
3078 | 2817 "dsn", MSIM_TYPE_INTEGER, MC_DELETE_CONTACT_INFO_DSN, |
3079 /** Callbacks called by Purple, to access this plugin. */ | 2818 "lid", MSIM_TYPE_INTEGER, MC_DELETE_CONTACT_INFO_LID, |
2819 "rid", MSIM_TYPE_INTEGER, session->next_rid++, | |
2820 "body", MSIM_TYPE_DICTIONARY, body, | |
2821 NULL); | |
2822 if (!msim_postprocess_outgoing(session, msg, name, "body", NULL)) | |
2823 purple_debug_error("myspace", "remove from block list command failed\n"); | |
2824 msim_msg_free(msg); | |
2825 | |
2826 /* Remove from our approve list and our block list */ | |
2827 msim_update_blocklist_for_buddy(session, name, FALSE, FALSE); | |
2828 } | |
2829 | |
2830 static void | |
2831 msim_buddy_free(PurpleBuddy *buddy) | |
2832 { | |
2833 msim_user_free(buddy->proto_data); | |
2834 buddy->proto_data = NULL; | |
2835 } | |
2836 | |
2837 /** | |
2838 * Returns a string of a username in canonical form. Basically removes all the | |
2839 * spaces, lowercases the string, and looks up user IDs to usernames. | |
2840 * Normalizing tom, TOM, Tom, and 6221 wil all return 'tom'. | |
2841 * | |
2842 * Borrowed this code from oscar_normalize. Added checking for | |
2843 * "if userid, get name before normalizing" | |
2844 */ | |
2845 static const char *msim_normalize(const PurpleAccount *account, const char *str) { | |
2846 static char normalized[BUF_LEN]; | |
2847 char *tmp1, *tmp2; | |
2848 int i, j; | |
2849 guint id; | |
2850 | |
2851 g_return_val_if_fail(str != NULL, NULL); | |
2852 | |
2853 if (msim_is_userid(str)) { | |
2854 /* Have user ID, we need to get their username first :) */ | |
2855 const char *username; | |
2856 | |
2857 /* If the account does not exist, we can't look up the user. */ | |
2858 if (!account || !account->gc) | |
2859 return str; | |
2860 | |
2861 id = atol(str); | |
2862 username = msim_uid2username_from_blist((PurpleAccount *)account, id); | |
2863 if (!username) { | |
2864 /* Not in buddy list... scheisse... TODO: Manual Lookup! Bug #4631 */ | |
2865 /* Note: manual lookup using msim_lookup_user() is a problem inside | |
2866 * msim_normalize(), because msim_lookup_user() calls a callback function | |
2867 * when the user information has been looked up, but msim_normalize() expects | |
2868 * the result immediately. */ | |
2869 strncpy(normalized, str, BUF_LEN); | |
2870 } else { | |
2871 strncpy(normalized, username, BUF_LEN); | |
2872 } | |
2873 } else { | |
2874 /* Have username. */ | |
2875 strncpy(normalized, str, BUF_LEN); | |
2876 } | |
2877 | |
2878 /* Strip spaces. */ | |
2879 for (i=0, j=0; normalized[j]; i++, j++) { | |
2880 while (normalized[j] == ' ') | |
2881 j++; | |
2882 normalized[i] = normalized[j]; | |
2883 } | |
2884 normalized[i] = '\0'; | |
2885 | |
2886 /* Lowercase and perform UTF-8 normalization. */ | |
2887 tmp1 = g_utf8_strdown(normalized, -1); | |
2888 tmp2 = g_utf8_normalize(tmp1, -1, G_NORMALIZE_DEFAULT); | |
2889 g_snprintf(normalized, sizeof(normalized), "%s", tmp2); | |
2890 g_free(tmp2); | |
2891 g_free(tmp1); | |
2892 | |
2893 /* TODO: re-add caps and spacing back to what the user wanted. | |
2894 * User can format their own names, for example 'msimprpl' is shown | |
2895 * as 'MsIm PrPl' in the official client. | |
2896 * | |
2897 * TODO: file a ticket to add this enhancement. | |
2898 */ | |
2899 | |
2900 return normalized; | |
2901 } | |
2902 | |
2903 /** | |
2904 * Return whether the buddy can be messaged while offline. | |
2905 * | |
2906 * The protocol supports offline messages in just the same way as online | |
2907 * messages. | |
2908 */ | |
2909 static gboolean | |
2910 msim_offline_message(const PurpleBuddy *buddy) | |
2911 { | |
2912 return TRUE; | |
2913 } | |
2914 | |
2915 /** | |
2916 * Send raw data to the server, possibly with embedded NULs. | |
2917 * | |
2918 * Used in prpl_info struct, so that plugins can have the most possible | |
2919 * control of what is sent over the connection. Inside this prpl, | |
2920 * msim_send_raw() is used, since it sends NUL-terminated strings (easier). | |
2921 * | |
2922 * @param gc PurpleConnection | |
2923 * @param buf Buffer to send | |
2924 * @param total_bytes Size of buffer to send | |
2925 * | |
2926 * @return Bytes successfully sent, or -1 on error. | |
2927 */ | |
2928 /* | |
2929 * TODO: This needs to do non-blocking writes and use a watcher to check | |
2930 * when the fd is available to be written to. | |
2931 */ | |
2932 static int | |
2933 msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes) | |
2934 { | |
2935 int total_bytes_sent; | |
2936 MsimSession *session; | |
2937 | |
2938 g_return_val_if_fail(gc != NULL, -1); | |
2939 g_return_val_if_fail(buf != NULL, -1); | |
2940 g_return_val_if_fail(total_bytes >= 0, -1); | |
2941 | |
2942 session = (MsimSession *)gc->proto_data; | |
2943 | |
2944 g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); | |
2945 | |
2946 /* Loop until all data is sent, or a failure occurs. */ | |
2947 total_bytes_sent = 0; | |
2948 do { | |
2949 int bytes_sent; | |
2950 | |
2951 bytes_sent = send(session->fd, buf + total_bytes_sent, | |
2952 total_bytes - total_bytes_sent, 0); | |
2953 | |
2954 if (bytes_sent < 0) { | |
2955 purple_debug_info("msim", "msim_send_raw(%s): send() failed: %s\n", | |
2956 buf, g_strerror(errno)); | |
2957 return total_bytes_sent; | |
2958 } | |
2959 total_bytes_sent += bytes_sent; | |
2960 | |
2961 } while(total_bytes_sent < total_bytes); | |
2962 | |
2963 return total_bytes_sent; | |
2964 } | |
2965 | |
2966 /** | |
2967 * Send raw data (given as a NUL-terminated string) to the server. | |
2968 * | |
2969 * @param session | |
2970 * @param msg The raw data to send, in a NUL-terminated string. | |
2971 * | |
2972 * @return TRUE if succeeded, FALSE if not. | |
2973 * | |
2974 */ | |
2975 gboolean | |
2976 msim_send_raw(MsimSession *session, const gchar *msg) | |
2977 { | |
2978 size_t len; | |
2979 | |
2980 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); | |
2981 g_return_val_if_fail(msg != NULL, FALSE); | |
2982 | |
2983 purple_debug_info("msim", "msim_send_raw: writing <%s>\n", msg); | |
2984 len = strlen(msg); | |
2985 | |
2986 return msim_send_really_raw(session->gc, msg, len) == len; | |
2987 } | |
2988 | |
2989 static GHashTable * | |
2990 msim_get_account_text_table(PurpleAccount *unused) | |
2991 { | |
2992 GHashTable *table; | |
2993 | |
2994 table = g_hash_table_new(g_str_hash, g_str_equal); | |
2995 | |
2996 g_hash_table_insert(table, "login_label", (gpointer)_("Email Address...")); | |
2997 | |
2998 return table; | |
2999 } | |
3000 | |
3001 /** | |
3002 * Callbacks called by Purple, to access this plugin. | |
3003 */ | |
3080 static PurplePluginProtocolInfo prpl_info = { | 3004 static PurplePluginProtocolInfo prpl_info = { |
3081 /* options */ | 3005 /* options */ |
3082 OPT_PROTO_USE_POINTSIZE /* specify font size in sane point size */ | 3006 OPT_PROTO_USE_POINTSIZE /* specify font size in sane point size */ |
3083 | OPT_PROTO_MAIL_CHECK, | 3007 | OPT_PROTO_MAIL_CHECK, |
3084 | 3008 |
3085 /* | OPT_PROTO_IM_IMAGE - TODO: direct images. */ | 3009 /* | OPT_PROTO_IM_IMAGE - TODO: direct images. */ |
3086 NULL, /* user_splits */ | 3010 NULL, /* user_splits */ |
3087 NULL, /* protocol_options */ | 3011 NULL, /* protocol_options */ |
3088 NO_BUDDY_ICONS, /* icon_spec - TODO: eventually should add this */ | 3012 NO_BUDDY_ICONS, /* icon_spec - TODO: eventually should add this */ |
3089 msim_list_icon, /* list_icon */ | 3013 msim_list_icon, /* list_icon */ |
3090 NULL, /* list_emblems */ | 3014 NULL, /* list_emblems */ |
3106 msim_add_buddy, /* add_buddy */ | 3030 msim_add_buddy, /* add_buddy */ |
3107 NULL, /* add_buddies */ | 3031 NULL, /* add_buddies */ |
3108 msim_remove_buddy, /* remove_buddy */ | 3032 msim_remove_buddy, /* remove_buddy */ |
3109 NULL, /* remove_buddies */ | 3033 NULL, /* remove_buddies */ |
3110 NULL, /* add_permit */ | 3034 NULL, /* add_permit */ |
3111 NULL, /* add_deny */ | 3035 msim_add_deny, /* add_deny */ |
3112 NULL, /* rem_permit */ | 3036 NULL, /* rem_permit */ |
3113 NULL, /* rem_deny */ | 3037 msim_rem_deny, /* rem_deny */ |
3114 NULL, /* set_permit_deny */ | 3038 NULL, /* set_permit_deny */ |
3115 NULL, /* join_chat */ | 3039 NULL, /* join_chat */ |
3116 NULL, /* reject chat invite */ | 3040 NULL, /* reject chat invite */ |
3117 NULL, /* get_chat_name */ | 3041 NULL, /* get_chat_name */ |
3118 NULL, /* chat_invite */ | 3042 NULL, /* chat_invite */ |
3124 NULL, /* get_cb_info */ | 3048 NULL, /* get_cb_info */ |
3125 NULL, /* get_cb_away */ | 3049 NULL, /* get_cb_away */ |
3126 NULL, /* alias_buddy */ | 3050 NULL, /* alias_buddy */ |
3127 NULL, /* group_buddy */ | 3051 NULL, /* group_buddy */ |
3128 NULL, /* rename_group */ | 3052 NULL, /* rename_group */ |
3129 NULL, /* buddy_free */ | 3053 msim_buddy_free, /* buddy_free */ |
3130 NULL, /* convo_closed */ | 3054 NULL, /* convo_closed */ |
3131 msim_normalize, /* normalize */ | 3055 msim_normalize, /* normalize */ |
3132 NULL, /* set_buddy_icon */ | 3056 NULL, /* set_buddy_icon */ |
3133 NULL, /* remove_group */ | 3057 NULL, /* remove_group */ |
3134 NULL, /* get_cb_real_name */ | 3058 NULL, /* get_cb_real_name */ |
3151 msim_get_account_text_table, /* get_account_text_table */ | 3075 msim_get_account_text_table, /* get_account_text_table */ |
3152 NULL, /* initiate_media */ | 3076 NULL, /* initiate_media */ |
3153 NULL /* can_do_media */ | 3077 NULL /* can_do_media */ |
3154 }; | 3078 }; |
3155 | 3079 |
3156 | 3080 /** |
3157 | 3081 * Load the plugin. |
3158 /** Based on MSN's plugin info comments. */ | 3082 */ |
3083 static gboolean | |
3084 msim_load(PurplePlugin *plugin) | |
3085 { | |
3086 /* If compiled to use RC4 from libpurple, check if it is really there. */ | |
3087 if (!purple_ciphers_find_cipher("rc4")) { | |
3088 purple_debug_error("msim", "rc4 not in libpurple, but it is required - not loading MySpaceIM plugin!\n"); | |
3089 purple_notify_error(plugin, _("Missing Cipher"), | |
3090 _("The RC4 cipher could not be found"), | |
3091 _("Upgrade " | |
3092 "to a libpurple with RC4 support (>= 2.0.1). MySpaceIM " | |
3093 "plugin will not be loaded.")); | |
3094 return FALSE; | |
3095 } | |
3096 return TRUE; | |
3097 } | |
3098 | |
3099 /** | |
3100 * Called when friends have been imported to buddy list on server. | |
3101 */ | |
3102 static void | |
3103 msim_import_friends_cb(MsimSession *session, const MsimMessage *reply, gpointer user_data) | |
3104 { | |
3105 MsimMessage *body; | |
3106 gchar *completed; | |
3107 | |
3108 /* Check if the friends were imported successfully. */ | |
3109 body = msim_msg_get_dictionary(reply, "body"); | |
3110 g_return_if_fail(body != NULL); | |
3111 completed = msim_msg_get_string(body, "Completed"); | |
3112 g_return_if_fail(body != NULL); | |
3113 msim_msg_free(body); | |
3114 if (!g_str_equal(completed, "True")) | |
3115 { | |
3116 purple_debug_info("msim_import_friends_cb", | |
3117 "failed to import friends: %s", completed); | |
3118 purple_notify_error(session->account, _("Add friends from MySpace.com"), | |
3119 _("Importing friends failed"), NULL); | |
3120 g_free(completed); | |
3121 return; | |
3122 } | |
3123 g_free(completed); | |
3124 | |
3125 purple_debug_info("msim_import_friends_cb", | |
3126 "added friends to server-side buddy list, requesting new contacts from server"); | |
3127 | |
3128 msim_get_contact_list(session, MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS); | |
3129 | |
3130 /* TODO: show, X friends have been added */ | |
3131 } | |
3132 | |
3133 /** | |
3134 * Import friends from myspace.com. | |
3135 */ | |
3136 static void msim_import_friends(PurplePluginAction *action) | |
3137 { | |
3138 PurpleConnection *gc; | |
3139 MsimSession *session; | |
3140 gchar *group_name; | |
3141 | |
3142 gc = (PurpleConnection *)action->context; | |
3143 session = (MsimSession *)gc->proto_data; | |
3144 | |
3145 group_name = "MySpace Friends"; | |
3146 | |
3147 g_return_if_fail(msim_send(session, | |
3148 "persist", MSIM_TYPE_INTEGER, 1, | |
3149 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, | |
3150 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_PUT, | |
3151 "dsn", MSIM_TYPE_INTEGER, MC_IMPORT_ALL_FRIENDS_DSN, | |
3152 "lid", MSIM_TYPE_INTEGER, MC_IMPORT_ALL_FRIENDS_LID, | |
3153 "uid", MSIM_TYPE_INTEGER, session->userid, | |
3154 "rid", MSIM_TYPE_INTEGER, | |
3155 msim_new_reply_callback(session, msim_import_friends_cb, NULL), | |
3156 "body", MSIM_TYPE_STRING, | |
3157 g_strdup_printf("GroupName=%s", group_name), | |
3158 NULL)); | |
3159 } | |
3160 | |
3161 /** | |
3162 * Actions menu for account. | |
3163 */ | |
3164 static GList * | |
3165 msim_actions(PurplePlugin *plugin, gpointer context) | |
3166 { | |
3167 PurpleConnection *gc; | |
3168 GList *menu; | |
3169 PurplePluginAction *act; | |
3170 | |
3171 gc = (PurpleConnection *)context; | |
3172 | |
3173 menu = NULL; | |
3174 | |
3175 #if 0 | |
3176 /* TODO: find out how */ | |
3177 act = purple_plugin_action_new(_("Find people..."), msim_); | |
3178 menu = g_list_append(menu, act); | |
3179 | |
3180 act = purple_plugin_action_new(_("Change IM name..."), NULL); | |
3181 menu = g_list_append(menu, act); | |
3182 #endif | |
3183 | |
3184 act = purple_plugin_action_new(_("Add friends from MySpace.com"), msim_import_friends); | |
3185 menu = g_list_append(menu, act); | |
3186 | |
3187 return menu; | |
3188 } | |
3189 | |
3190 /** | |
3191 * Based on MSN's plugin info comments. | |
3192 */ | |
3159 static PurplePluginInfo info = { | 3193 static PurplePluginInfo info = { |
3160 PURPLE_PLUGIN_MAGIC, | 3194 PURPLE_PLUGIN_MAGIC, |
3161 PURPLE_MAJOR_VERSION, | 3195 PURPLE_MAJOR_VERSION, |
3162 PURPLE_MINOR_VERSION, | 3196 PURPLE_MINOR_VERSION, |
3163 PURPLE_PLUGIN_PROTOCOL, /**< type */ | 3197 PURPLE_PLUGIN_PROTOCOL, /**< type */ |
3187 NULL, /**< reserved2 */ | 3221 NULL, /**< reserved2 */ |
3188 NULL, /**< reserved3 */ | 3222 NULL, /**< reserved3 */ |
3189 NULL /**< reserved4 */ | 3223 NULL /**< reserved4 */ |
3190 }; | 3224 }; |
3191 | 3225 |
3192 | |
3193 #ifdef MSIM_SELF_TEST | 3226 #ifdef MSIM_SELF_TEST |
3194 /** Test functions. | 3227 /* |
3228 * Test functions. | |
3195 * Used to test or try out the internal workings of msimprpl. If you're reading | 3229 * Used to test or try out the internal workings of msimprpl. If you're reading |
3196 * this code for the first time, these functions can be instructive in learning | 3230 * this code for the first time, these functions can be instructive in learning |
3197 * how msimprpl is architected. | 3231 * how msimprpl is architected. |
3198 */ | 3232 */ |
3199 void | 3233 |
3200 msim_test_all(void) { | 3234 /** |
3201 guint failures; | 3235 * Test MsimMessage for basic functionality. |
3202 | 3236 */ |
3203 | 3237 static int |
3204 failures = 0; | |
3205 failures += msim_test_msg(); | |
3206 failures += msim_test_escaping(); | |
3207 | |
3208 if (failures) { | |
3209 purple_debug_info("msim", "msim_test_all HAD FAILURES: %d\n", failures); | |
3210 } else { | |
3211 purple_debug_info("msim", "msim_test_all - all tests passed!\n"); | |
3212 } | |
3213 exit(0); | |
3214 } | |
3215 | |
3216 /** Test MsimMessage for basic functionality. */ | |
3217 int | |
3218 msim_test_msg(void) | 3238 msim_test_msg(void) |
3219 { | 3239 { |
3220 MsimMessage *msg, *msg_cloned, *msg2; | 3240 MsimMessage *msg, *msg_cloned, *msg2; |
3221 GList *list; | 3241 GList *list; |
3222 gchar *packed, *packed_expected, *packed_cloned; | 3242 gchar *packed, *packed_expected, *packed_cloned; |
3289 msim_msg_free(msg2); | 3309 msim_msg_free(msg2); |
3290 | 3310 |
3291 return failures; | 3311 return failures; |
3292 } | 3312 } |
3293 | 3313 |
3294 /** Test protocol-level escaping/unescaping. */ | 3314 /** |
3295 int | 3315 * Test protocol-level escaping/unescaping. |
3316 */ | |
3317 static int | |
3296 msim_test_escaping(void) | 3318 msim_test_escaping(void) |
3297 { | 3319 { |
3298 guint failures; | 3320 guint failures; |
3299 gchar *raw, *escaped, *unescaped, *expected; | 3321 gchar *raw, *escaped, *unescaped, *expected; |
3300 | 3322 |
3321 ++failures, raw, unescaped); | 3343 ++failures, raw, unescaped); |
3322 } | 3344 } |
3323 | 3345 |
3324 return failures; | 3346 return failures; |
3325 } | 3347 } |
3348 | |
3349 static void | |
3350 msim_test_all(void) | |
3351 { | |
3352 guint failures; | |
3353 | |
3354 failures = 0; | |
3355 failures += msim_test_msg(); | |
3356 failures += msim_test_escaping(); | |
3357 | |
3358 if (failures) { | |
3359 purple_debug_info("msim", "msim_test_all HAD FAILURES: %d\n", failures); | |
3360 } else { | |
3361 purple_debug_info("msim", "msim_test_all - all tests passed!\n"); | |
3362 } | |
3363 exit(0); | |
3364 } | |
3326 #endif | 3365 #endif |
3366 | |
3367 #ifdef MSIM_CHECK_NEWER_VERSION | |
3368 /** | |
3369 * Callback for when a currentversion.txt has been downloaded. | |
3370 */ | |
3371 static void | |
3372 msim_check_newer_version_cb(PurpleUtilFetchUrlData *url_data, | |
3373 gpointer user_data, | |
3374 const gchar *url_text, | |
3375 gsize len, | |
3376 const gchar *error_message) | |
3377 { | |
3378 GKeyFile *keyfile; | |
3379 GError *error; | |
3380 GString *data; | |
3381 gchar *newest_filever; | |
3382 | |
3383 if (!url_text) { | |
3384 purple_debug_info("msim_check_newer_version_cb", | |
3385 "got error: %s\n", error_message); | |
3386 return; | |
3387 } | |
3388 | |
3389 purple_debug_info("msim_check_newer_version_cb", | |
3390 "url_text=%s\n", url_text ? url_text : "(NULL)"); | |
3391 | |
3392 /* Prepend [group] so that GKeyFile can parse it (requires a group). */ | |
3393 data = g_string_new(url_text); | |
3394 purple_debug_info("msim", "data=%s\n", data->str | |
3395 ? data->str : "(NULL)"); | |
3396 data = g_string_prepend(data, "[group]\n"); | |
3397 | |
3398 purple_debug_info("msim", "data=%s\n", data->str | |
3399 ? data->str : "(NULL)"); | |
3400 | |
3401 /* url_text is variable=data\n...†*/ | |
3402 | |
3403 /* Check FILEVER, 1.0.716.0. 716 is build, MSIM_CLIENT_VERSION */ | |
3404 /* New (english) version can be downloaded from SETUPURL+SETUPFILE */ | |
3405 | |
3406 error = NULL; | |
3407 keyfile = g_key_file_new(); | |
3408 | |
3409 /* Default list seperator is ;, but currentversion.txt doesn't have | |
3410 * these, so set to an unused character to avoid parsing problems. */ | |
3411 g_key_file_set_list_separator(keyfile, '\0'); | |
3412 | |
3413 g_key_file_load_from_data(keyfile, data->str, data->len, | |
3414 G_KEY_FILE_NONE, &error); | |
3415 g_string_free(data, TRUE); | |
3416 | |
3417 if (error != NULL) { | |
3418 purple_debug_info("msim_check_newer_version_cb", | |
3419 "couldn't parse, error: %d %d %s\n", | |
3420 error->domain, error->code, error->message); | |
3421 g_error_free(error); | |
3422 return; | |
3423 } | |
3424 | |
3425 gchar **ks; | |
3426 guint n; | |
3427 ks = g_key_file_get_keys(keyfile, "group", &n, NULL); | |
3428 purple_debug_info("msim", "n=%d\n", n); | |
3429 guint i; | |
3430 for (i = 0; ks[i] != NULL; ++i) | |
3431 { | |
3432 purple_debug_info("msim", "%d=%s\n", i, ks[i]); | |
3433 } | |
3434 | |
3435 newest_filever = g_key_file_get_string(keyfile, "group", | |
3436 "FILEVER", &error); | |
3437 | |
3438 purple_debug_info("msim_check_newer_version_cb", | |
3439 "newest filever: %s\n", newest_filever ? | |
3440 newest_filever : "(NULL)"); | |
3441 if (error != NULL) { | |
3442 purple_debug_info("msim_check_newer_version_cb", | |
3443 "error: %d %d %s\n", | |
3444 error->domain, error->code, error->message); | |
3445 g_error_free(error); | |
3446 } | |
3447 | |
3448 g_key_file_free(keyfile); | |
3449 | |
3450 exit(0); | |
3451 } | |
3452 #endif | |
3453 | |
3454 /** | |
3455 Handle a myim:addContact command, after username has been looked up. | |
3456 */ | |
3457 static void | |
3458 msim_uri_handler_addContact_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) | |
3459 { | |
3460 MsimMessage *body; | |
3461 gchar *username; | |
3462 | |
3463 body = msim_msg_get_dictionary(userinfo, "body"); | |
3464 username = msim_msg_get_string(body, "UserName"); | |
3465 msim_msg_free(body); | |
3466 | |
3467 if (!username) { | |
3468 guint uid; | |
3469 | |
3470 uid = msim_msg_get_integer(userinfo, "UserID"); | |
3471 g_return_if_fail(uid != 0); | |
3472 | |
3473 username = g_strdup_printf("%d", uid); | |
3474 } | |
3475 | |
3476 purple_blist_request_add_buddy(session->account, username, _("Buddies"), NULL); | |
3477 | |
3478 g_free(username); | |
3479 } | |
3480 | |
3481 /* TODO: move uid->username resolving to IM sending and buddy adding functions, | |
3482 * so that user can manually add or IM by userid and username automatically | |
3483 * looked up if possible? */ | |
3484 | |
3485 /** | |
3486 * Handle a myim:sendIM URI command, after username has been looked up. | |
3487 */ | |
3488 static void | |
3489 msim_uri_handler_sendIM_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) | |
3490 { | |
3491 PurpleConversation *conv; | |
3492 MsimMessage *body; | |
3493 gchar *username; | |
3494 | |
3495 body = msim_msg_get_dictionary(userinfo, "body"); | |
3496 username = msim_msg_get_string(body, "UserName"); | |
3497 msim_msg_free(body); | |
3498 | |
3499 if (!username) { | |
3500 guint uid; | |
3501 | |
3502 uid = msim_msg_get_integer(userinfo, "UserID"); | |
3503 g_return_if_fail(uid != 0); | |
3504 | |
3505 username = g_strdup_printf("%d", uid); | |
3506 } | |
3507 | |
3508 | |
3509 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, session->account); | |
3510 if (!conv) { | |
3511 purple_debug_info("msim_uri_handler", "creating new conversation for %s\n", username); | |
3512 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, session->account, username); | |
3513 } | |
3514 | |
3515 /* Just open the window so the user can send an IM. */ | |
3516 purple_conversation_present(conv); | |
3517 | |
3518 g_free(username); | |
3519 } | |
3327 | 3520 |
3328 static gboolean | 3521 static gboolean |
3329 msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params) | 3522 msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params) |
3330 { | 3523 { |
3331 PurpleAccount *account; | 3524 PurpleAccount *account; |
3349 | 3542 |
3350 /* TODO: if auto=true, "Add all the people on this page to my IM List!", on | 3543 /* TODO: if auto=true, "Add all the people on this page to my IM List!", on |
3351 * http://collect.myspace.com/index.cfm?fuseaction=im.friendslist. Don't need a cid. */ | 3544 * http://collect.myspace.com/index.cfm?fuseaction=im.friendslist. Don't need a cid. */ |
3352 | 3545 |
3353 /* Convert numeric contact ID back to a string. Needed for looking up. Don't just | 3546 /* Convert numeric contact ID back to a string. Needed for looking up. Don't just |
3354 * directly use cid directly from parameters, because it might not be numeric. | 3547 * directly use cid directly from parameters, because it might not be numeric. |
3355 * It is trivial to change this to allow cID to be a username, but that's not how | 3548 * It is trivial to change this to allow cID to be a username, but that's not how |
3356 * the official MySpaceIM client works, so don't provide that functionality. */ | 3549 * the official MySpaceIM client works, so don't provide that functionality. */ |
3357 cid_str = g_strdup_printf("%d", cid); | 3550 cid_str = g_strdup_printf("%d", cid); |
3358 | 3551 |
3359 | 3552 |
3368 } | 3561 } |
3369 l = l->next; | 3562 l = l->next; |
3370 } | 3563 } |
3371 | 3564 |
3372 if (!account) { | 3565 if (!account) { |
3373 purple_notify_error(NULL, _("myim URL handler"), | 3566 purple_notify_error(NULL, _("myim URL handler"), |
3374 _("No suitable MySpaceIM account could be found to open this myim URL."), | 3567 _("No suitable MySpaceIM account could be found to open this myim URL."), |
3375 _("Enable the proper MySpaceIM account and try again.")); | 3568 _("Enable the proper MySpaceIM account and try again.")); |
3376 g_free(cid_str); | 3569 g_free(cid_str); |
3377 return FALSE; | 3570 return FALSE; |
3378 } | 3571 } |
3379 | 3572 |
3380 session = (MsimSession *)account->gc->proto_data; | 3573 session = (MsimSession *)account->gc->proto_data; |
3381 g_return_val_if_fail(session != NULL, FALSE); | 3574 g_return_val_if_fail(session != NULL, FALSE); |
3382 | 3575 |
3383 /* Lookup userid to username. TODO: push this down, to IM sending/contact | 3576 /* Lookup userid to username. TODO: push this down, to IM sending/contact |
3384 * adding functions. */ | 3577 * adding functions. */ |
3385 | 3578 |
3386 /* myim:sendIM?uID=USERID&cID=CONTACTID */ | 3579 /* myim:sendIM?uID=USERID&cID=CONTACTID */ |
3387 if (!g_ascii_strcasecmp(cmd, "sendIM")) { | 3580 if (!g_ascii_strcasecmp(cmd, "sendIM")) { |
3388 msim_lookup_user(session, cid_str, (MSIM_USER_LOOKUP_CB)msim_uri_handler_sendIM_cb, NULL); | 3581 msim_lookup_user(session, cid_str, (MSIM_USER_LOOKUP_CB)msim_uri_handler_sendIM_cb, NULL); |
3397 } | 3590 } |
3398 | 3591 |
3399 return FALSE; | 3592 return FALSE; |
3400 } | 3593 } |
3401 | 3594 |
3402 /* TODO: move uid->username resolving to IM sending and buddy adding functions, | 3595 /** |
3403 * so that user can manually add or IM by userid and username automatically | 3596 * Initialize plugin. |
3404 * looked up if possible? */ | 3597 */ |
3405 | |
3406 /** Handle a myim:sendIM URI command, after username has been looked up. */ | |
3407 static void | 3598 static void |
3408 msim_uri_handler_sendIM_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) | 3599 init_plugin(PurplePlugin *plugin) |
3409 { | |
3410 PurpleConversation *conv; | |
3411 MsimMessage *body; | |
3412 gchar *username; | |
3413 | |
3414 body = msim_msg_get_dictionary(userinfo, "body"); | |
3415 username = msim_msg_get_string(body, "UserName"); | |
3416 msim_msg_free(body); | |
3417 | |
3418 if (!username) { | |
3419 guint uid; | |
3420 | |
3421 uid = msim_msg_get_integer(userinfo, "UserID"); | |
3422 g_return_if_fail(uid != 0); | |
3423 | |
3424 username = g_strdup_printf("%d", uid); | |
3425 } | |
3426 | |
3427 | |
3428 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, session->account); | |
3429 if (!conv) { | |
3430 purple_debug_info("msim_uri_handler", "creating new conversation for %s\n", username); | |
3431 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, session->account, username); | |
3432 } | |
3433 | |
3434 /* Just open the window so the user can send an IM. */ | |
3435 purple_conversation_present(conv); | |
3436 | |
3437 g_free(username); | |
3438 } | |
3439 | |
3440 /** Handle a myim:addContact command, after username has been looked up. */ | |
3441 static void | |
3442 msim_uri_handler_addContact_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) | |
3443 { | |
3444 MsimMessage *body; | |
3445 gchar *username; | |
3446 | |
3447 body = msim_msg_get_dictionary(userinfo, "body"); | |
3448 username = msim_msg_get_string(body, "UserName"); | |
3449 msim_msg_free(body); | |
3450 | |
3451 if (!username) { | |
3452 guint uid; | |
3453 | |
3454 uid = msim_msg_get_integer(userinfo, "UserID"); | |
3455 g_return_if_fail(uid != 0); | |
3456 | |
3457 username = g_strdup_printf("%d", uid); | |
3458 } | |
3459 | |
3460 | |
3461 purple_blist_request_add_buddy(session->account, username, _("Buddies"), NULL); | |
3462 | |
3463 g_free(username); | |
3464 } | |
3465 | |
3466 /** Initialize plugin. */ | |
3467 void | |
3468 init_plugin(PurplePlugin *plugin) | |
3469 { | 3600 { |
3470 #ifdef MSIM_SELF_TEST | 3601 #ifdef MSIM_SELF_TEST |
3471 msim_test_all(); | 3602 msim_test_all(); |
3472 exit(0); | 3603 exit(0); |
3473 #endif /* MSIM_SELF_TEST */ | 3604 #endif /* MSIM_SELF_TEST */ |
3514 option = purple_account_option_int_new(_("Base font size (points)"), "base_font_size", MSIM_BASE_FONT_POINT_SIZE); | 3645 option = purple_account_option_int_new(_("Base font size (points)"), "base_font_size", MSIM_BASE_FONT_POINT_SIZE); |
3515 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); | 3646 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); |
3516 #endif | 3647 #endif |
3517 | 3648 |
3518 /* Code below only runs once. Based on oscar.c's oscar_init(). */ | 3649 /* Code below only runs once. Based on oscar.c's oscar_init(). */ |
3519 if (initialized) | 3650 if (initialized) |
3520 return; | 3651 return; |
3521 | 3652 |
3522 initialized = TRUE; | 3653 initialized = TRUE; |
3523 | 3654 |
3524 purple_signal_connect(purple_get_core(), "uri-handler", &initialized, | 3655 purple_signal_connect(purple_get_core(), "uri-handler", &initialized, |