Mercurial > pidgin
changeset 25758:b535b9ffb929
propagate from branch 'im.pidgin.pidgin' (head 6b951d62be418d5bfc437a1ab747c6ba900d7979)
to branch 'im.pidgin.cpw.malu.xmpp.ibb_ft' (head 95e1ae61eda292e6ca5f636d5fc76bac8262940b)
author | Marcus Lundblad <ml@update.uu.se> |
---|---|
date | Tue, 16 Dec 2008 19:18:41 +0000 |
parents | 476466aca5e0 (diff) 4b51394fd834 (current diff) |
children | 69cf692c09f5 |
files | |
diffstat | 16 files changed, 3989 insertions(+), 4495 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Tue Dec 16 19:17:44 2008 +0000 +++ b/ChangeLog Tue Dec 16 19:18:41 2008 +0000 @@ -4,26 +4,29 @@ libpurple: * Corrected maximum message lengths for Yahoo! * The Buddy State Notification plugin no longer prints duplicate - notifications when the same buddy is in multiple groups (Florian Quèze) - * The Buddy State Notification plugin no longer turns JID's, MSN Passport - ID's, etc. into links (Florian Quèze) - * purple-remote now has a "getstatusmessage" command to retrieve the text - of the current status message. + notifications when the same buddy is in multiple groups (Florian + Quèze) + * The Buddy State Notification plugin no longer turns JID's, MSN + Passport ID's, etc. into links (Florian Quèze) + * purple-remote now has a "getstatusmessage" command to retrieve + the text of the current status message. * Various fixes to the nullprpl (Paul Aurich) * Fix a crash when accessing the roomlist for an account that's not connected (Paul Aurich) - * Fix a crash in purple_accounts_delete that happens when this function is - called before the buddy list is initialized (Florian Quèze) - * Fix use of av_len in perl bindings to fix some off-by-one bugs (Paul - Aurich) - * On ICQ, advertise the ICQ 6 typing capability. This should fix the - reports of typing notifications not working with third-party clients - (Jaromír Karmazín) + * Fix a crash in purple_accounts_delete that happens when this + function is called before the buddy list is initialized + (Florian Quèze) + * Fix use of av_len in perl bindings to fix some off-by-one bugs + (Paul Aurich) + * On ICQ, advertise the ICQ 6 typing capability. This should fix + the reports of typing notifications not working with third-party + clients (Jaromír Karmazín) * Many QQ fixes and improvements, including the ability to connect using QQ2008 protocol and sending/receiving of long messages. * Fix a crash with DNS SRV lookups (Florian Quèze) - * Fix insanely long idle times for Sametime 7.5 buddies by assuming 0 idle - time if the idle timestamp is in the future (Laurent Montaron) + * Fix insanely long idle times for Sametime 7.5 buddies by assuming + 0 idle time if the idle timestamp is in the future (Laurent + Montaron) Gadu-Gadu: * Fix some problems with Gadu-Gadu buddy icons (Adam Strzelecki) @@ -32,46 +35,47 @@ Strzelecki) MSN: - * Fix an error with offline messages by shipping the *new* "Microsoft - Secure Server Authority" and the "Microsoft Internet Authority" - certificates. These are now always installed even when using - --with-system-ssl-certs because most systems don't ship those - intermediate certificates. - * The Games and Office media can now be set and displayed (in addition - to the previous Music media). The Media status text now shows the - album, if possible. + * Fix an error with offline messages by shipping the *new* + "Microsoft Secure Server Authority" and the "Microsoft Internet + Authority" certificates. These are now always installed even when + using --with-system-ssl-certs because most systems don't ship + those intermediate certificates. + * The Games and Office media can now be set and displayed (in + addition to the previous Music media). The Media status text now + shows the album, if possible. * Messages sent from a mobile device while you were offline are now correctly received. - * Server transfers after you've been connected for a long time should - now be handled correctly. + * Server transfers after you've been connected for a long time + should now be handled correctly. * Many other fixes and code cleanup. SIMPLE: * Fix a crash when a malformed message is received. - * Don't allow connecting accounts if no server name has been specified - (Florian Quèze) + * Don't allow connecting accounts if no server name has been + specified (Florian Quèze) XMPP: - * Fix the namespace URL we look for in PEP reply stanzas to match the URL - used in the 'get' requests (Paul Aurich) + * Fix the namespace URL we look for in PEP reply stanzas to match + the URL used in the 'get' requests (Paul Aurich) * Resources can be set to the local machine's hostname by using __HOSTNAME__ as the resource string (Jonathan Sailor) * Resources can now be left blank, causing the server to generate a resource for us where supported (Jonathan Sailor) * Resources now default to no value * Quit trying to get user info for MUC's (Paul Aurich) - * Send "client-accepts-full-bind-result" attribute during SASL login. - This will fix Google Talk login failures if the user configures the - wrong domain for his/her account. - * Support new <metadata/> element to indicate no XEP-0084 User Avatar - (Paul Aurich) - * Fix SHA1 avatar checksum errors that occur when one of the bytes in a - checksum begins with 0 (Paul Aurich) - + * Send "client-accepts-full-bind-result" attribute during SASL + login. This will fix Google Talk login failures if the user + configures the wrong domain for his/her account. + * Support new <metadata/> element to indicate no XEP-0084 User + Avatar (Paul Aurich) + * Fix SHA1 avatar checksum errors that occur when one of the bytes + in a checksum begins with 0 (Paul Aurich) + Zephyr: * Enable auto-reply, to emulate 'zaway' (Toby Schaffer) - * Fix a crash when an account is configured to use tzc but tzc is not - installed or the configured tzc command is invalid (Michael Terry) + * Fix a crash when an account is configured to use tzc but tzc is + not installed or the configured tzc command is invalid (Michael + Terry) * Fix a 10 second delay waiting on tzc if it is not installed or the configured command is invalid (Michael Terry) @@ -81,13 +85,18 @@ previously changed that pref, add a line like this to ~/.purple/gtkrc-2.0 (where 500 is the timeout (in ms) you want): gtk-tooltip-timeout = 500 - To completely disable tooltips (e.g. if you had an old tooltip_delay - of zero), add this to ~/.purple/gtkrc-2.0: + To completely disable tooltips (e.g. if you had an old + tooltip_delay of zero), add this to ~/.purple/gtkrc-2.0: gtk-enable-tooltips = 0 * Moved the release notification dialog to a mini-dialog in the buddylist. (Thanks to Casey Ho) - * Fix a crash when closing an authorization minidialog with the X then - immediately going offline (Paul Aurich) + * Fix a crash when closing an authorization minidialog with the X + then immediately going offline (Paul Aurich) + * Fix a crash when closing Pidgin related to custom smileys (disassociate + smileys from GtkIMHtml widgets when they are destroyed). + * Fix adding custom a custom smiley using the context menu from a + conversation in the case when no custom smiley had been added before + using the smiley manager. Finch: * Allow binding meta+arrow keys for actions.
--- a/libpurple/protocols/msn/nexus.c Tue Dec 16 19:17:44 2008 +0000 +++ b/libpurple/protocols/msn/nexus.c Tue Dec 16 19:18:41 2008 +0000 @@ -434,6 +434,9 @@ #endif char *decrypted_data; + if (resp == NULL) + return; + purple_debug_info("msn", "Got Update Response for %s.\n", ticket_domains[ud->id][SSO_VALID_TICKET_DOMAIN]); enckey = xmlnode_get_child(resp->xml, "Header/Security/DerivedKeyToken");
--- a/libpurple/protocols/myspace/markup.c Tue Dec 16 19:17:44 2008 +0000 +++ b/libpurple/protocols/myspace/markup.c Tue Dec 16 19:18:41 2008 +0000 @@ -193,7 +193,7 @@ decor_str = xmlnode_get_attrib(root, "s"); /* Validate the font face, to avoid constructing invalid HTML later */ - if (strchr(face, '\'') != NULL) + if (face != NULL && strchr(face, '\'') != NULL) face = NULL; height = height_str != NULL ? atol(height_str) : 12;
--- a/libpurple/protocols/myspace/message.c Tue Dec 16 19:17:44 2008 +0000 +++ b/libpurple/protocols/myspace/message.c Tue Dec 16 19:18:41 2008 +0000 @@ -1005,7 +1005,7 @@ * @return MsimMessage *. Caller should msim_msg_free() when done. */ MsimMessage * -msim_parse(gchar *raw) +msim_parse(const gchar *raw) { MsimMessage *msg; gchar *token; @@ -1026,7 +1026,6 @@ "missing initial backslash: <%s>\n", raw); /* XXX: Should we try to recover, and read to first backslash? */ - g_free(raw); return NULL; } @@ -1057,9 +1056,6 @@ } g_strfreev(tokens); - /* Can free now since all data was copied to hash key/values */ - g_free(raw); - return msg; } @@ -1214,8 +1210,8 @@ * * @return A new MsimMessage *. Must msim_msg_free() when done. */ -MsimMessage * -msim_msg_dictionary_parse(gchar *raw) +static MsimMessage * +msim_msg_dictionary_parse(const gchar *raw) { MsimMessage *dict; gchar *item; @@ -1275,7 +1271,7 @@ return msim_msg_clone((MsimMessage *)elem->data); case MSIM_TYPE_RAW: - return msim_msg_dictionary_parse((gchar *)elem->data); + return msim_msg_dictionary_parse(elem->data); default: purple_debug_info("msim_msg_get_dictionary", "type %d unknown, name %s\n",
--- a/libpurple/protocols/myspace/message.h Tue Dec 16 19:17:44 2008 +0000 +++ b/libpurple/protocols/myspace/message.h Tue Dec 16 19:18:41 2008 +0000 @@ -91,8 +91,7 @@ gboolean msim_msg_send(struct _MsimSession *session, MsimMessage *msg); -MsimMessage *msim_parse(gchar *raw); -MsimMessage *msim_msg_dictionary_parse(gchar *raw); +MsimMessage *msim_parse(const gchar *raw); MsimMessageElement *msim_msg_get(MsimMessage *msg, const gchar *name);
--- a/libpurple/protocols/myspace/myspace.c Tue Dec 16 19:17:44 2008 +0000 +++ b/libpurple/protocols/myspace/myspace.c Tue Dec 16 19:18:41 2008 +0000 @@ -1,4 +1,5 @@ -/* MySpaceIM Protocol Plugin +/** + * MySpaceIM Protocol Plugin * * \author Jeff Connelly * @@ -35,87 +36,430 @@ #include "myspace.h" -/* Internal functions */ - -#ifdef MSIM_DEBUG_MSG -static void print_hash_item(gpointer key, gpointer value, gpointer user_data); -#endif - -static int msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes); -static gboolean msim_login_challenge(MsimSession *session, MsimMessage *msg); -static gchar *msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], const gchar *email, const gchar *password, guint *response_len); - -static gboolean msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg); -static gboolean msim_incoming_bm(MsimSession *session, MsimMessage *msg); -static gboolean msim_incoming_status(MsimSession *session, MsimMessage *msg); -static gboolean msim_incoming_im(MsimSession *session, MsimMessage *msg); -/* static gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg); - in zap.c */ -static gboolean msim_incoming_action(MsimSession *session, MsimMessage *msg); -static gboolean msim_incoming_media(MsimSession *session, MsimMessage *msg); -static gboolean msim_incoming_unofficial_client(MsimSession *session, - MsimMessage *msg); - -#ifdef MSIM_SEND_CLIENT_VERSION -static gboolean msim_send_unofficial_client(MsimSession *session, gchar *username); -#endif - -static void msim_get_info_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); - -static void msim_set_status_code(MsimSession *session, guint code, gchar *statstring); - -static gboolean msim_process_server_info(MsimSession *session, MsimMessage *msg); -static gboolean msim_web_challenge(MsimSession *session, MsimMessage *msg); -static gboolean msim_process_reply(MsimSession *session, MsimMessage *msg); - -static gboolean msim_preprocess_incoming(MsimSession *session, MsimMessage *msg); - -#ifdef MSIM_USE_KEEPALIVE -static gboolean msim_check_alive(gpointer data); -#endif - -static gboolean msim_is_username_set(MsimSession *session, MsimMessage *msg); - -static gboolean msim_process(MsimSession *session, MsimMessage *msg); - -static MsimMessage *msim_do_postprocessing(MsimMessage *msg, const gchar *uid_field_name, const gchar *uid_before, guint uid); -static void msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); -static gboolean msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, const gchar *username, const gchar *uid_field_name, const gchar *uid_before); - -static gboolean msim_error(MsimSession *session, MsimMessage *msg); - -static void msim_check_inbox_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); -static gboolean msim_check_inbox(gpointer data); - -static void msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond); - - -static void msim_connect_cb(gpointer data, gint source, const gchar *error_message); - -static void msim_import_friends(PurplePluginAction *action); -static void msim_import_friends_cb(MsimSession *session, MsimMessage *reply, gpointer user_data); -static gboolean msim_get_contact_list(MsimSession *session, int what_to_do_after); - -static gboolean msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params); -static void msim_uri_handler_addContact_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); -static void msim_uri_handler_sendIM_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); - -/** - * Load the plugin. +static void msim_set_status(PurpleAccount *account, PurpleStatus *status); +static void msim_set_idle(PurpleConnection *gc, int time); + +/** + * Perform actual postprocessing on a message, adding userid as specified. + * + * @param msg The message to postprocess. + * @param uid_before Name of field where to insert new field before, or NULL for end. + * @param uid_field_name Name of field to add uid to. + * @param uid The userid to insert. + * + * If the field named by uid_field_name already exists, then its string contents will + * be used for the field, except "<uid>" will be replaced by the userid. + * + * If the field named by uid_field_name does not exist, it will be added before the + * field named by uid_before, as an integer, with the userid. + * + * Does not handle sending, or scheduling userid lookup. For that, see msim_postprocess_outgoing(). + */ +static MsimMessage * +msim_do_postprocessing(MsimMessage *msg, const gchar *uid_before, + const gchar *uid_field_name, guint uid) +{ + MsimMessageElement *elem; + msim_msg_dump("msim_do_postprocessing msg: %s\n", msg); + + /* First, check - if the field already exists, replace <uid> within it */ + if ((elem = msim_msg_get(msg, uid_field_name)) != NULL) { + gchar *fmt_string; + gchar *uid_str, *new_str; + + /* Get the packed element, flattening it. This allows <uid> to be + * replaced within nested data structures, since the replacement is done + * on the linear, packed data, not on a complicated data structure. + * + * For example, if the field was originally a dictionary or a list, you + * would have to iterate over all the items in it to see what needs to + * be replaced. But by packing it first, the <uid> marker is easily replaced + * just by a string replacement. + */ + fmt_string = msim_msg_pack_element_data(elem); + + uid_str = g_strdup_printf("%d", uid); + new_str = purple_strreplace(fmt_string, "<uid>", uid_str); + g_free(uid_str); + g_free(fmt_string); + + /* Free the old element data */ + msim_msg_free_element_data(elem->data); + + /* Replace it with our new data */ + elem->data = new_str; + elem->type = MSIM_TYPE_RAW; + + } else { + /* Otherwise, insert new field into outgoing message. */ + msg = msim_msg_insert_before(msg, uid_before, uid_field_name, MSIM_TYPE_INTEGER, GUINT_TO_POINTER(uid)); + } + + msim_msg_dump("msim_postprocess_outgoing_cb: postprocessed msg=%s\n", msg); + + return msg; +} + +/** + * Callback for msim_postprocess_outgoing() to add a userid to a message, and send it (once receiving userid). + * + * @param session + * @param userinfo The user information reply message, containing the user ID + * @param data The message to postprocess and send. + * + * The data message should contain these fields: + * + * _uid_field_name: string, name of field to add with userid from userinfo message + * _uid_before: string, name of field before field to insert, or NULL for end + */ +static void +msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, + gpointer data) +{ + gchar *uid_field_name, *uid_before, *username; + guint uid; + MsimMessage *msg, *body; + + msg = (MsimMessage *)data; + + msim_msg_dump("msim_postprocess_outgoing_cb() got msg=%s\n", msg); + + /* Obtain userid from userinfo message. */ + body = msim_msg_get_dictionary(userinfo, "body"); + g_return_if_fail(body != NULL); + + uid = msim_msg_get_integer(body, "UserID"); + msim_msg_free(body); + + username = msim_msg_get_string(msg, "_username"); + + if (!uid) { + gchar *msg; + + msg = g_strdup_printf(_("No such user: %s"), username); + if (!purple_conv_present_error(username, session->account, msg)) { + purple_notify_error(NULL, NULL, _("User lookup"), msg); + } + + g_free(msg); + g_free(username); + /* TODO: free + * msim_msg_free(msg); + */ + return; + } + + uid_field_name = msim_msg_get_string(msg, "_uid_field_name"); + uid_before = msim_msg_get_string(msg, "_uid_before"); + + msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid); + + /* Send */ + if (!msim_msg_send(session, msg)) { + msim_msg_dump("msim_postprocess_outgoing_cb: sending failed for message: %s\n", msg); + } + + + /* Free field names AFTER sending message, because MsimMessage does NOT copy + * field names - instead, treats them as static strings (which they usually are). + */ + g_free(uid_field_name); + g_free(uid_before); + g_free(username); + /* TODO: free + * msim_msg_free(msg); + */ +} + +/** + * Postprocess and send a message. + * + * @param session + * @param msg Message to postprocess. Will NOT be freed. + * @param username Username to resolve. Assumed to be a static string (will not be freed or copied). + * @param uid_field_name Name of new field to add, containing uid of username. Static string. + * @param uid_before Name of existing field to insert username field before. Static string. + * + * @return TRUE if successful. */ -gboolean -msim_load(PurplePlugin *plugin) +static gboolean +msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, + const gchar *username, const gchar *uid_field_name, + const gchar *uid_before) +{ + PurpleBuddy *buddy; + guint uid; + gboolean rc; + + g_return_val_if_fail(msg != NULL, FALSE); + + /* Store information for msim_postprocess_outgoing_cb(). */ + msim_msg_dump("msim_postprocess_outgoing: msg before=%s\n", msg); + msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username)); + msg = msim_msg_append(msg, "_uid_field_name", MSIM_TYPE_STRING, g_strdup(uid_field_name)); + msg = msim_msg_append(msg, "_uid_before", MSIM_TYPE_STRING, g_strdup(uid_before)); + + /* First, try the most obvious. If numeric userid is given, use that directly. */ + if (msim_is_userid(username)) { + uid = atol(username); + } else { + /* Next, see if on buddy list and know uid. */ + buddy = purple_find_buddy(session->account, username); + if (buddy) { + uid = purple_blist_node_get_int(&buddy->node, "UserID"); + } else { + uid = 0; + } + + if (!buddy || !uid) { + /* Don't have uid offhand - need to ask for it, and wait until hear back before sending. */ + purple_debug_info("msim", ">>> msim_postprocess_outgoing: couldn't find username %s in blist\n", + username ? username : "(NULL)"); + msim_msg_dump("msim_postprocess_outgoing - scheduling lookup, msg=%s\n", msg); + /* TODO: where is cloned message freed? Should be in _cb. */ + msim_lookup_user(session, username, msim_postprocess_outgoing_cb, msim_msg_clone(msg)); + return TRUE; /* not sure of status yet - haven't sent! */ + } + } + + /* Already have uid, postprocess and send msg immediately. */ + purple_debug_info("msim", "msim_postprocess_outgoing: found username %s has uid %d\n", + username ? username : "(NULL)", uid); + + msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid); + + msim_msg_dump("msim_postprocess_outgoing: msg after (uid immediate)=%s\n", msg); + + rc = msim_msg_send(session, msg); + + /* TODO: free + * msim_msg_free(msg); + */ + + return rc; +} + +/** + * Send a buddy message of a given type. + * + * @param session + * @param who Username to send message to. + * @param text Message text to send. Not freed; will be copied. + * @param type A MSIM_BM_* constant. + * + * @return TRUE if success, FALSE if fail. + * + * Buddy messages ('bm') include instant messages, action messages, status messages, etc. + */ +gboolean +msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, + int type) +{ + gboolean rc; + MsimMessage *msg; + const gchar *from_username; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(who != NULL, FALSE); + g_return_val_if_fail(text != NULL, FALSE); + + from_username = session->account->username; + + g_return_val_if_fail(from_username != NULL, FALSE); + + purple_debug_info("msim", "sending %d message from %s to %s: %s\n", + type, from_username, who, text); + + msg = msim_msg_new( + "bm", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(type), + "sesskey", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(session->sesskey), + /* 't' will be inserted here */ + "cv", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(MSIM_CLIENT_VERSION), + "msg", MSIM_TYPE_STRING, g_strdup(text), + NULL); + + rc = msim_postprocess_outgoing(session, msg, who, "t", "cv"); + + msim_msg_free(msg); + + return rc; +} + +/** + * Lookup a username by userid, from buddy list. + * + * @param wanted_uid + * + * @return Username of wanted_uid, if on blist, or NULL. + * This is a static string, so don't free it. Copy it if needed. + * + */ +static const gchar * +msim_uid2username_from_blist(PurpleAccount *account, guint wanted_uid) { - /* If compiled to use RC4 from libpurple, check if it is really there. */ - if (!purple_ciphers_find_cipher("rc4")) { - purple_debug_error("msim", "rc4 not in libpurple, but it is required - not loading MySpaceIM plugin!\n"); - purple_notify_error(plugin, _("Missing Cipher"), - _("The RC4 cipher could not be found"), - _("Upgrade " - "to a libpurple with RC4 support (>= 2.0.1). MySpaceIM " - "plugin will not be loaded.")); - return FALSE; + GSList *buddies, *cur; + const gchar *ret; + + buddies = purple_find_buddies(account, NULL); + + if (!buddies) + { + purple_debug_info("msim", "msim_uid2username_from_blist: no buddies?\n"); + return NULL; + } + + ret = NULL; + + for (cur = buddies; cur != NULL; cur = g_slist_next(cur)) + { + PurpleBuddy *buddy; + guint uid; + const gchar *name; + + /* See finch/gnthistory.c */ + buddy = cur->data; + + uid = purple_blist_node_get_int(&buddy->node, "UserID"); + name = purple_buddy_get_name(buddy); + + if (uid == wanted_uid) + { + ret = name; + break; + } } - return TRUE; + + g_slist_free(buddies); + return ret; +} + +/** + * Setup a callback, to be called when a reply is received with the returned rid. + * + * @param cb The callback, an MSIM_USER_LOOKUP_CB. + * @param data Arbitrary user data to be passed to callback (probably an MsimMessage *). + * + * @return The request/reply ID, used to link replies with requests, or -1. + * Put the rid in your request, 'rid' field. + * + * TODO: Make more generic and more specific: + * 1) MSIM_USER_LOOKUP_CB - make it for PERSIST_REPLY, not just user lookup + * 2) data - make it an MsimMessage? + */ +guint +msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb, + gpointer data) +{ + guint rid; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); + + rid = session->next_rid++; + + g_hash_table_insert(session->user_lookup_cb, GUINT_TO_POINTER(rid), cb); + g_hash_table_insert(session->user_lookup_cb_data, GUINT_TO_POINTER(rid), data); + + return rid; +} + +/** + * Return the icon name for a buddy and account. + * + * @param acct The account to find the icon for, or NULL for protocol icon. + * @param buddy The buddy to find the icon for, or NULL for the account icon. + * + * @return The base icon name string. + */ +static const gchar * +msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy) +{ + /* Use a MySpace icon submitted by hbons at + * http://developer.pidgin.im/wiki/MySpaceIM. */ + return "myspace"; +} + +/** + * Obtain the status text for a buddy. + * + * @param buddy The buddy to obtain status text for. + * + * @return Status text, or NULL if error. Caller g_free()'s. + */ +static char * +msim_status_text(PurpleBuddy *buddy) +{ + MsimSession *session; + MsimUser *user; + const gchar *display_name, *headline; + + g_return_val_if_fail(buddy != NULL, NULL); + + user = msim_get_user_from_buddy(buddy); + + session = (MsimSession *)buddy->account->gc->proto_data; + g_return_val_if_fail(MSIM_SESSION_VALID(session), NULL); + + display_name = headline = NULL; + + /* Retrieve display name and/or headline, depending on user preference. */ + if (purple_account_get_bool(session->account, "show_headline", TRUE)) { + headline = user->headline; + } + + if (purple_account_get_bool(session->account, "show_display_name", FALSE)) { + display_name = user->display_name; + } + + /* Return appropriate combination of display name and/or headline, or neither. */ + + if (display_name && headline) { + return g_strconcat(display_name, " ", headline, NULL); + } else if (display_name) { + return g_strdup(display_name); + } else if (headline) { + return g_strdup(headline); + } + + return NULL; +} + +/** + * Obtain the tooltip text for a buddy. + * + * @param buddy Buddy to obtain tooltip text on. + * @param user_info Variable modified to have the tooltip text. + * @param full TRUE if should obtain full tooltip text. + */ +static void +msim_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, + gboolean full) +{ + MsimUser *user; + + g_return_if_fail(buddy != NULL); + g_return_if_fail(user_info != NULL); + + user = msim_get_user_from_buddy(buddy); + + if (PURPLE_BUDDY_IS_ONLINE(buddy)) { + MsimSession *session; + + session = (MsimSession *)buddy->account->gc->proto_data; + + g_return_if_fail(MSIM_SESSION_VALID(session)); + + /* TODO: if (full), do something different? */ + + /* TODO: request information? have to figure out how to do + * the asynchronous lookup like oscar does (tooltip shows + * 'retrieving...' if not yet available, then changes when it is). + * + * Right now, only show what we have on hand. + */ + + /* Show abbreviated user info. */ + msim_append_user_info(session, user_info, user, FALSE); + } } /** @@ -123,7 +467,7 @@ * * @return GList of status types. */ -GList * +static GList * msim_status_types(PurpleAccount *acct) { GList *types; @@ -177,154 +521,141 @@ } /** - * Return the icon name for a buddy and account. - * - * @param acct The account to find the icon for, or NULL for protocol icon. - * @param buddy The buddy to find the icon for, or NULL for the account icon. + * Compute the base64'd login challenge response based on username, password, nonce, and IPs. * - * @return The base icon name string. - */ -const gchar * -msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy) -{ - /* Use a MySpace icon submitted by hbons at - * http://developer.pidgin.im/wiki/MySpaceIM. */ - return "myspace"; -} - -#ifdef MSIM_DEBUG_MSG -static void -print_hash_item(gpointer key, gpointer value, gpointer user_data) -{ - purple_debug_info("msim", "%s=%s\n", - key ? (gchar *)key : "(NULL)", - value ? (gchar *)value : "(NULL)"); -} -#endif - -/** - * Send raw data (given as a NUL-terminated string) to the server. + * @param nonce The base64 encoded nonce ('nc') field from the server. + * @param email User's email address (used as login name). + * @param password User's cleartext password. + * @param response_len Will be written with response length. * - * @param session - * @param msg The raw data to send, in a NUL-terminated string. - * - * @return TRUE if succeeded, FALSE if not. - * + * @return Binary login challenge response, ready to send to the server. + * Must be g_free()'d when finished. NULL if error. */ -gboolean -msim_send_raw(MsimSession *session, const gchar *msg) -{ - g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); - g_return_val_if_fail(msg != NULL, FALSE); - - purple_debug_info("msim", "msim_send_raw: writing <%s>\n", msg); - - return msim_send_really_raw(session->gc, msg, strlen(msg)) == - strlen(msg); -} - -/** Send raw data to the server, possibly with embedded NULs. - * - * Used in prpl_info struct, so that plugins can have the most possible - * control of what is sent over the connection. Inside this prpl, - * msim_send_raw() is used, since it sends NUL-terminated strings (easier). - * - * @param gc PurpleConnection - * @param buf Buffer to send - * @param total_bytes Size of buffer to send - * - * @return Bytes successfully sent, or -1 on error. - */ -static int -msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes) +static gchar * +msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], + const gchar *email, const gchar *password, guint *response_len) { - int total_bytes_sent; - MsimSession *session; - - g_return_val_if_fail(gc != NULL, -1); - g_return_val_if_fail(buf != NULL, -1); - g_return_val_if_fail(total_bytes >= 0, -1); - - session = (MsimSession *)gc->proto_data; - - g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); - - /* Loop until all data is sent, or a failure occurs. */ - total_bytes_sent = 0; - do { - int bytes_sent; - - bytes_sent = send(session->fd, buf + total_bytes_sent, - total_bytes - total_bytes_sent, 0); - - if (bytes_sent < 0) { - purple_debug_info("msim", "msim_send_raw(%s): send() failed: %s\n", - buf, g_strerror(errno)); - return total_bytes_sent; - } - total_bytes_sent += bytes_sent; - - } while(total_bytes_sent < total_bytes); - - return total_bytes_sent; -} - - -/** - * Start logging in to the MSIM servers. - * - * @param acct Account information to use to login. - */ -void -msim_login(PurpleAccount *acct) -{ - PurpleConnection *gc; - const gchar *host; - int port; - - g_return_if_fail(acct != NULL); - g_return_if_fail(acct->username != NULL); - - purple_debug_info("msim", "logging in %s\n", acct->username); - - gc = purple_account_get_connection(acct); - gc->proto_data = msim_session_new(acct); - gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_NO_URLDESC; - - /* 1. connect to server */ - purple_connection_update_progress(gc, _("Connecting"), - 0, /* which connection step this is */ - 4); /* total number of steps */ - - host = purple_account_get_string(acct, "server", MSIM_SERVER); - port = purple_account_get_int(acct, "port", MSIM_PORT); - - /* From purple.sf.net/api: - * """Note that this function name can be misleading--although it is called - * "proxy connect," it is used for establishing any outgoing TCP connection, - * whether through a proxy or not.""" */ - - /* Calls msim_connect_cb when connected. */ - if (!purple_proxy_connect(gc, acct, host, port, msim_connect_cb, gc)) { - /* TODO: try other ports if in auto mode, then save - * working port and try that first next time. */ - purple_connection_error_reason (gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Couldn't create socket")); - return; + PurpleCipherContext *key_context; + PurpleCipher *sha1; + PurpleCipherContext *rc4; + + guchar hash_pw[HASH_SIZE]; + guchar key[HASH_SIZE]; + gchar *password_utf16le, *password_utf8_lc; + GString *data; + guchar *data_out; + size_t data_out_len; + gsize conv_bytes_read, conv_bytes_written; + GError *conv_error; +#ifdef MSIM_DEBUG_LOGIN_CHALLENGE + int i; +#endif + + g_return_val_if_fail(nonce != NULL, NULL); + g_return_val_if_fail(email != NULL, NULL); + g_return_val_if_fail(password != NULL, NULL); + g_return_val_if_fail(response_len != NULL, NULL); + + /* Convert password to lowercase (required for passwords containing + * uppercase characters). MySpace passwords are lowercase, + * see ticket #2066. */ + password_utf8_lc = g_utf8_strdown(password, -1); + + /* Convert ASCII password to UTF16 little endian */ + purple_debug_info("msim", "converting password to UTF-16LE\n"); + conv_error = NULL; + password_utf16le = g_convert(password_utf8_lc, -1, "UTF-16LE", "UTF-8", + &conv_bytes_read, &conv_bytes_written, &conv_error); + g_free(password_utf8_lc); + + g_return_val_if_fail(conv_bytes_read == strlen(password), NULL); + + if (conv_error != NULL) { + purple_debug_error("msim", + "g_convert password UTF8->UTF16LE failed: %s", + conv_error->message); + g_error_free(conv_error); + return NULL; } + + /* Compute password hash */ + purple_cipher_digest_region("sha1", (guchar *)password_utf16le, + conv_bytes_written, sizeof(hash_pw), hash_pw, NULL); + g_free(password_utf16le); + +#ifdef MSIM_DEBUG_LOGIN_CHALLENGE + purple_debug_info("msim", "pwhash = "); + for (i = 0; i < sizeof(hash_pw); i++) + purple_debug_info("msim", "%.2x ", hash_pw[i]); + purple_debug_info("msim", "\n"); +#endif + + /* key = sha1(sha1(pw) + nonce2) */ + sha1 = purple_ciphers_find_cipher("sha1"); + key_context = purple_cipher_context_new(sha1, NULL); + purple_cipher_context_append(key_context, hash_pw, HASH_SIZE); + purple_cipher_context_append(key_context, (guchar *)(nonce + NONCE_SIZE), NONCE_SIZE); + purple_cipher_context_digest(key_context, sizeof(key), key, NULL); + purple_cipher_context_destroy(key_context); + +#ifdef MSIM_DEBUG_LOGIN_CHALLENGE + purple_debug_info("msim", "key = "); + for (i = 0; i < sizeof(key); i++) { + purple_debug_info("msim", "%.2x ", key[i]); + } + purple_debug_info("msim", "\n"); +#endif + + rc4 = purple_cipher_context_new_by_name("rc4", NULL); + + /* Note: 'key' variable is 0x14 bytes (from SHA-1 hash), + * but only first 0x10 used for the RC4 key. */ + purple_cipher_context_set_option(rc4, "key_len", (gpointer)0x10); + purple_cipher_context_set_key(rc4, key); + + /* TODO: obtain IPs of network interfaces */ + + /* rc4 encrypt: + * nonce1+email+IP list */ + + data = g_string_new(NULL); + g_string_append_len(data, nonce, NONCE_SIZE); + g_string_append(data, email); + g_string_append_len(data, MSIM_LOGIN_IP_LIST, MSIM_LOGIN_IP_LIST_LEN); + + data_out = g_new0(guchar, data->len); + + purple_cipher_context_encrypt(rc4, (const guchar *)data->str, + data->len, data_out, &data_out_len); + purple_cipher_context_destroy(rc4); + + if (data_out_len != data->len) { + purple_debug_info("msim", "msim_compute_login_response: " + "data length mismatch: %" G_GSIZE_FORMAT " != %" + G_GSIZE_FORMAT "\n", data_out_len, data->len); + } + + g_string_free(data, TRUE); + +#ifdef MSIM_DEBUG_LOGIN_CHALLENGE + purple_debug_info("msim", "response=<%s>\n", data_out); +#endif + + *response_len = data_out_len; + + return (gchar *)data_out; } /** - * Process a login challenge, sending a response. + * Process a login challenge, sending a response. * - * @param session + * @param session * @param msg Login challenge message. * * @return TRUE if successful, FALSE if not */ -static gboolean -msim_login_challenge(MsimSession *session, MsimMessage *msg) +static gboolean +msim_login_challenge(MsimSession *session, MsimMessage *msg) { PurpleAccount *account; gchar *response; @@ -383,363 +714,16 @@ } /** - * Compute the base64'd login challenge response based on username, password, nonce, and IPs. - * - * @param nonce The base64 encoded nonce ('nc') field from the server. - * @param email User's email address (used as login name). - * @param password User's cleartext password. - * @param response_len Will be written with response length. - * - * @return Binary login challenge response, ready to send to the server. - * Must be g_free()'d when finished. NULL if error. - */ -static gchar * -msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], - const gchar *email, const gchar *password, guint *response_len) -{ - PurpleCipherContext *key_context; - PurpleCipher *sha1; - PurpleCipherContext *rc4; - - guchar hash_pw[HASH_SIZE]; - guchar key[HASH_SIZE]; - gchar *password_utf16le, *password_utf8_lc; - guchar *data; - guchar *data_out; - size_t data_len, data_out_len; - gsize conv_bytes_read, conv_bytes_written; - GError *conv_error; -#ifdef MSIM_DEBUG_LOGIN_CHALLENGE - int i; -#endif - - g_return_val_if_fail(nonce != NULL, NULL); - g_return_val_if_fail(email != NULL, NULL); - g_return_val_if_fail(password != NULL, NULL); - g_return_val_if_fail(response_len != NULL, NULL); - - /* Convert password to lowercase (required for passwords containing - * uppercase characters). MySpace passwords are lowercase, - * see ticket #2066. */ - password_utf8_lc = g_utf8_strdown(password, -1); - - /* Convert ASCII password to UTF16 little endian */ - purple_debug_info("msim", "converting password to UTF-16LE\n"); - conv_error = NULL; - password_utf16le = g_convert(password_utf8_lc, -1, "UTF-16LE", "UTF-8", - &conv_bytes_read, &conv_bytes_written, &conv_error); - g_free(password_utf8_lc); - - g_return_val_if_fail(conv_bytes_read == strlen(password), NULL); - - if (conv_error != NULL) { - purple_debug_error("msim", - "g_convert password UTF8->UTF16LE failed: %s", - conv_error->message); - g_error_free(conv_error); - return NULL; - } - - /* Compute password hash */ - purple_cipher_digest_region("sha1", (guchar *)password_utf16le, - conv_bytes_written, sizeof(hash_pw), hash_pw, NULL); - g_free(password_utf16le); - -#ifdef MSIM_DEBUG_LOGIN_CHALLENGE - purple_debug_info("msim", "pwhash = "); - for (i = 0; i < sizeof(hash_pw); i++) - purple_debug_info("msim", "%.2x ", hash_pw[i]); - purple_debug_info("msim", "\n"); -#endif - - /* key = sha1(sha1(pw) + nonce2) */ - sha1 = purple_ciphers_find_cipher("sha1"); - key_context = purple_cipher_context_new(sha1, NULL); - purple_cipher_context_append(key_context, hash_pw, HASH_SIZE); - purple_cipher_context_append(key_context, (guchar *)(nonce + NONCE_SIZE), NONCE_SIZE); - purple_cipher_context_digest(key_context, sizeof(key), key, NULL); - purple_cipher_context_destroy(key_context); - -#ifdef MSIM_DEBUG_LOGIN_CHALLENGE - purple_debug_info("msim", "key = "); - for (i = 0; i < sizeof(key); i++) { - purple_debug_info("msim", "%.2x ", key[i]); - } - purple_debug_info("msim", "\n"); -#endif - - rc4 = purple_cipher_context_new_by_name("rc4", NULL); - - /* Note: 'key' variable is 0x14 bytes (from SHA-1 hash), - * but only first 0x10 used for the RC4 key. */ - purple_cipher_context_set_option(rc4, "key_len", (gpointer)0x10); - purple_cipher_context_set_key(rc4, key); - - /* TODO: obtain IPs of network interfaces */ - - /* rc4 encrypt: - * nonce1+email+IP list */ - - data_len = NONCE_SIZE + strlen(email) + MSIM_LOGIN_IP_LIST_LEN; - data = g_new0(guchar, data_len); - memcpy(data, nonce, NONCE_SIZE); - memcpy(data + NONCE_SIZE, email, strlen(email)); - memcpy(data + NONCE_SIZE + strlen(email), MSIM_LOGIN_IP_LIST, MSIM_LOGIN_IP_LIST_LEN); - - data_out = g_new0(guchar, data_len); - - purple_cipher_context_encrypt(rc4, (const guchar *)data, - data_len, data_out, &data_out_len); - purple_cipher_context_destroy(rc4); - g_free(data); - - if (data_out_len != data_len) { - purple_debug_info("msim", "msim_compute_login_response: " - "data length mismatch: %" G_GSIZE_FORMAT " != %" - G_GSIZE_FORMAT "\n", data_out_len, data_len); - } - -#ifdef MSIM_DEBUG_LOGIN_CHALLENGE - purple_debug_info("msim", "response=<%s>\n", data_out); -#endif - - *response_len = data_out_len; - - return (gchar *)data_out; -} - -/** - * Schedule an IM to be sent once the user ID is looked up. - * - * @param gc Connection. - * @param who A user id, email, or username to send the message to. - * @param message Instant message text to send. - * @param flags Flags. - * - * @return 1 if successful or postponed, -1 if failed - * - * Allows sending to a user by username, email address, or userid. If - * a username or email address is given, the userid must be looked up. - * This function does that by calling msim_postprocess_outgoing(). - */ -int -msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, - PurpleMessageFlags flags) -{ - MsimSession *session; - gchar *message_msim; - int rc; - - g_return_val_if_fail(gc != NULL, -1); - g_return_val_if_fail(who != NULL, -1); - g_return_val_if_fail(message != NULL, -1); - - /* 'flags' has many options, not used here. */ - - session = (MsimSession *)gc->proto_data; - - g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); - - message_msim = html_to_msim_markup(session, message); - - if (msim_send_bm(session, who, message_msim, MSIM_BM_INSTANT)) { - /* Return 1 to have Purple show this IM as being sent, 0 to not. I always - * return 1 even if the message could not be sent, since I don't know if - * it has failed yet--because the IM is only sent after the userid is - * retrieved from the server (which happens after this function returns). - * If an error does occur, it should be logged to the IM window. - */ - rc = 1; - } else { - rc = -1; - } - - g_free(message_msim); - - return rc; -} - -/** Send a buddy message of a given type. - * - * @param session - * @param who Username to send message to. - * @param text Message text to send. Not freed; will be copied. - * @param type A MSIM_BM_* constant. - * - * @return TRUE if success, FALSE if fail. - * - * Buddy messages ('bm') include instant messages, action messages, status messages, etc. - * - */ -gboolean -msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, - int type) -{ - gboolean rc; - MsimMessage *msg; - const gchar *from_username; - - g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); - g_return_val_if_fail(who != NULL, FALSE); - g_return_val_if_fail(text != NULL, FALSE); - - from_username = session->account->username; - - g_return_val_if_fail(from_username != NULL, FALSE); - - purple_debug_info("msim", "sending %d message from %s to %s: %s\n", - type, from_username, who, text); - - msg = msim_msg_new( - "bm", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(type), - "sesskey", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(session->sesskey), - /* 't' will be inserted here */ - "cv", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(MSIM_CLIENT_VERSION), - "msg", MSIM_TYPE_STRING, g_strdup(text), - NULL); - - rc = msim_postprocess_outgoing(session, msg, who, "t", "cv"); - - msim_msg_free(msg); - - return rc; -} - - -/** Record the client version in the buddy list, from an incoming message. */ -static gboolean -msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg) -{ - gchar *username, *cv; - gboolean ret; - MsimUser *user; - - username = msim_msg_get_string(msg, "_username"); - cv = msim_msg_get_string(msg, "cv"); - - g_return_val_if_fail(username != NULL, FALSE); - if (!cv) { - /* No client version to record, don't worry about it. */ - g_free(username); - return FALSE; - } - - user = msim_find_user(session, username); - - if (user) { - user->client_cv = atol(cv); - ret = TRUE; - } else { - ret = FALSE; - } - - g_free(username); - g_free(cv); - - return ret; -} - -/** Handle an incoming buddy message. */ -static gboolean -msim_incoming_bm(MsimSession *session, MsimMessage *msg) -{ - guint bm; - - bm = msim_msg_get_integer(msg, "bm"); - - msim_incoming_bm_record_cv(session, msg); - - switch (bm) { - case MSIM_BM_STATUS: - return msim_incoming_status(session, msg); - case MSIM_BM_INSTANT: - return msim_incoming_im(session, msg); - case MSIM_BM_ACTION: - return msim_incoming_action(session, msg); - case MSIM_BM_MEDIA: - return msim_incoming_media(session, msg); - case MSIM_BM_UNOFFICIAL_CLIENT: - return msim_incoming_unofficial_client(session, msg); - default: - /* Not really an IM, but show it for informational - * purposes during development. */ - return msim_incoming_im(session, msg); - } -} - -/** - * Handle an incoming instant message. - * - * @param session The session - * @param msg Message from the server, containing 'f' (userid from) and 'msg'. - * Should also contain username in _username from preprocessing. - * - * @return TRUE if successful. - */ -static gboolean -msim_incoming_im(MsimSession *session, MsimMessage *msg) -{ - gchar *username, *msg_msim_markup, *msg_purple_markup; - gchar *userid; - time_t time_received; - PurpleConversation *conv; - - g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); - g_return_val_if_fail(msg != NULL, FALSE); - - username = msim_msg_get_string(msg, "_username"); - /* I know this isn't really a string... but we need it to be one for - * purple_find_conversation_with_account(). */ - userid = msim_msg_get_string(msg, "f"); - g_return_val_if_fail(username != NULL, FALSE); - - purple_debug_info("msim_incoming_im", "UserID is %s", userid); - - if (msim_is_userid(username)) { - purple_debug_info("msim", "Ignoring message from spambot (%s) on account %s\n", - username, purple_account_get_username(session->account)); - g_free(username); - return FALSE; - } - - /* See if a conversation with their UID already exists...*/ - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, userid, session->account); - if (conv) { - /* Since the conversation exists... We need to normalize it */ - purple_conversation_set_name(conv, username); - } - - msg_msim_markup = msim_msg_get_string(msg, "msg"); - g_return_val_if_fail(msg_msim_markup != NULL, FALSE); - - msg_purple_markup = msim_markup_to_html(session, msg_msim_markup); - g_free(msg_msim_markup); - - time_received = msim_msg_get_integer(msg, "date"); - if (!time_received) { - purple_debug_info("msim_incoming_im", "date in message not set.\n"); - time_received = time(NULL); - } - - serv_got_im(session->gc, username, msg_purple_markup, PURPLE_MESSAGE_RECV, time_received); - - g_free(username); - g_free(msg_purple_markup); - - return TRUE; -} - -/** * Process unrecognized information. * * @param session * @param msg An MsimMessage that was unrecognized, or NULL. * @param note Information on what was unrecognized, or NULL. */ -void +void msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note) { - /* TODO: Some more context, outwardly equivalent to a backtrace, + /* TODO: Some more context, outwardly equivalent to a backtrace, * for helping figure out what this msg is for. What was going on? * But not too much information so that a user * posting this dump reveals confidential information. @@ -747,13 +731,13 @@ /* TODO: dump unknown msgs to file, so user can send them to me * if they wish, to help add support for new messages (inspired - * by Alexandr Shutko, who maintains OSCAR protocol documentation). + * by Alexandr Shutko, who maintains OSCAR protocol documentation). * * Filed enhancement ticket for libpurple as #4688. */ - purple_debug_info("msim", "Unrecognized data on account for %s\n", - (session && session->account && session->account->username) ? + purple_debug_info("msim", "Unrecognized data on account for %s\n", + (session && session->account && session->account->username) ? session->account->username : "(NULL)"); if (note) { purple_debug_info("msim", "(Note: %s)\n", note); @@ -764,562 +748,62 @@ } } -/** - * Handle an incoming action message. - * - * @param session - * @param msg - * - * @return TRUE if successful. - * - */ -static gboolean -msim_incoming_action(MsimSession *session, MsimMessage *msg) -{ - gchar *msg_text, *username; - gboolean rc; - - g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); - g_return_val_if_fail(msg != NULL, FALSE); - - msg_text = msim_msg_get_string(msg, "msg"); - g_return_val_if_fail(msg_text != NULL, FALSE); - - username = msim_msg_get_string(msg, "_username"); - g_return_val_if_fail(username != NULL, FALSE); - - purple_debug_info("msim", "msim_incoming_action: action <%s> from <%s>\n", - msg_text, username); - - if (g_str_equal(msg_text, "%typing%")) { - serv_got_typing(session->gc, username, 0, PURPLE_TYPING); - rc = TRUE; - } else if (g_str_equal(msg_text, "%stoptyping%")) { - serv_got_typing_stopped(session->gc, username); - rc = TRUE; - } else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_")) { - rc = msim_incoming_zap(session, msg); - } else if (strstr(msg_text, "!!!GroupCount=")) { - /* TODO: support group chats. I think the number in msg_text has - * something to do with the 'gid' field. */ - purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text); - - rc = TRUE; - } else if (strstr(msg_text, "!!!Offline=")) { - /* TODO: support group chats. This one might mean a user - * went offline or exited the chat. */ - purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text); - - rc = TRUE; - } else if (msim_msg_get_integer(msg, "aid") != 0) { - purple_debug_info("msim", "TODO: implement #4691, group chat from %d on %d: %s\n", - msim_msg_get_integer(msg, "aid"), - msim_msg_get_integer(msg, "f"), - msg_text); - - rc = TRUE; - } else { - msim_unrecognized(session, msg, - "got to msim_incoming_action but unrecognized value for 'msg'"); - rc = FALSE; - } - - g_free(msg_text); - g_free(username); - - return rc; -} - -/* Process an incoming media (message background?) message. */ -static gboolean -msim_incoming_media(MsimSession *session, MsimMessage *msg) -{ - gchar *username, *text; - - username = msim_msg_get_string(msg, "_username"); - text = msim_msg_get_string(msg, "msg"); - - g_return_val_if_fail(username != NULL, FALSE); - g_return_val_if_fail(text != NULL, FALSE); - - purple_debug_info("msim", "msim_incoming_media: from %s, got msg=%s\n", username, text); - - /* Media messages are sent when the user opens a window to someone. - * Tell libpurple they started typing and stopped typing, to inform the Psychic - * Mode plugin so it too can open a window to the user. */ - serv_got_typing(session->gc, username, 0, PURPLE_TYPING); - serv_got_typing_stopped(session->gc, username); - - g_free(username); - - return TRUE; -} - -/* Process an incoming "unofficial client" message. The plugin for - * Miranda IM sends this message with the plugin information. */ -static gboolean -msim_incoming_unofficial_client(MsimSession *session, MsimMessage *msg) -{ - MsimUser *user; - gchar *username, *client_info; - - username = msim_msg_get_string(msg, "_username"); - client_info = msim_msg_get_string(msg, "msg"); - - g_return_val_if_fail(username != NULL, FALSE); - g_return_val_if_fail(client_info != NULL, FALSE); - - purple_debug_info("msim", "msim_incoming_unofficial_client: %s is using client %s\n", - username, client_info); - - user = msim_find_user(session, username); - - g_return_val_if_fail(user != NULL, FALSE); - - if (user->client_info) { - g_free(user->client_info); - } - user->client_info = client_info; - - g_free(username); - /* Do not free client_info - the MsimUser now owns it. */ - - return TRUE; -} - - -#ifdef MSIM_SEND_CLIENT_VERSION -/** Send our client version to another unofficial client that understands it. */ +/** Called when the session key arrives to check whether the user + * has a username, and set one if desired. */ static gboolean -msim_send_unofficial_client(MsimSession *session, gchar *username) -{ - gchar *our_info; - gboolean ret; - - our_info = g_strdup_printf("Libpurple %d.%d.%d - msimprpl %s", - PURPLE_MAJOR_VERSION, - PURPLE_MINOR_VERSION, - PURPLE_MICRO_VERSION, - MSIM_PRPL_VERSION_STRING); - - ret = msim_send_bm(session, username, our_info, MSIM_BM_UNOFFICIAL_CLIENT); - - return ret; -} -#endif - -/** - * Handle when our user starts or stops typing to another user. - * - * @param gc - * @param name The buddy name to which our user is typing to - * @param state PURPLE_TYPING, PURPLE_TYPED, PURPLE_NOT_TYPING - * - * @return 0 - */ -unsigned int -msim_send_typing(PurpleConnection *gc, const gchar *name, - PurpleTypingState state) -{ - const gchar *typing_str; - MsimSession *session; - - g_return_val_if_fail(gc != NULL, 0); - g_return_val_if_fail(name != NULL, 0); - - session = (MsimSession *)gc->proto_data; - - g_return_val_if_fail(MSIM_SESSION_VALID(session), 0); - - switch (state) { - case PURPLE_TYPING: - typing_str = "%typing%"; - break; - - case PURPLE_TYPED: - case PURPLE_NOT_TYPING: - default: - typing_str = "%stoptyping%"; - break; - } - - purple_debug_info("msim", "msim_send_typing(%s): %d (%s)\n", name, state, typing_str); - msim_send_bm(session, name, typing_str, MSIM_BM_ACTION); - return 0; -} - - - -/** Callback for msim_get_info(), for when user info is received. */ -static void -msim_get_info_cb(MsimSession *session, MsimMessage *user_info_msg, - gpointer data) -{ - MsimMessage *msg; - gchar *username; - PurpleNotifyUserInfo *user_info; - MsimUser *user; - - g_return_if_fail(MSIM_SESSION_VALID(session)); - - /* Get user{name,id} from msim_get_info, passed as an MsimMessage for - orthogonality. */ - msg = (MsimMessage *)data; - g_return_if_fail(msg != NULL); - - username = msim_msg_get_string(msg, "user"); - if (!username) { - purple_debug_info("msim", "msim_get_info_cb: no 'user' in msg\n"); - return; - } - - msim_msg_free(msg); - purple_debug_info("msim", "msim_get_info_cb: got for user: %s\n", username); - - user = msim_find_user(session, username); - - if (!user) { - /* User isn't on blist, create a temporary user to store info. */ - user = g_new0(MsimUser, 1); - user->temporary_user = TRUE; - } - - /* Update user structure with new information */ - msim_store_user_info(session, user_info_msg, user); - - user_info = purple_notify_user_info_new(); - - /* Append data from MsimUser to PurpleNotifyUserInfo for display, full */ - msim_append_user_info(session, user_info, user, TRUE); - - purple_notify_userinfo(session->gc, username, user_info, NULL, NULL); - purple_debug_info("msim", "msim_get_info_cb: username=%s\n", username); - - purple_notify_user_info_destroy(user_info); - - if (user->temporary_user) { - g_free(user->client_info); - g_free(user->gender); - g_free(user->location); - g_free(user->headline); - g_free(user->display_name); - g_free(user->username); - g_free(user->image_url); - g_free(user); - } - g_free(username); -} - -/** Retrieve a user's profile. - * @param username Username, user ID, or email address to lookup. - */ -void -msim_get_info(PurpleConnection *gc, const gchar *username) -{ - MsimSession *session; - MsimUser *user; - gchar *user_to_lookup; - MsimMessage *user_msg; - - g_return_if_fail(gc != NULL); - g_return_if_fail(username != NULL); - - session = (MsimSession *)gc->proto_data; - - g_return_if_fail(MSIM_SESSION_VALID(session)); - - /* Obtain uid of buddy. */ - user = msim_find_user(session, username); - - /* If is on buddy list, lookup by uid since it is faster. */ - if (user && user->id) { - user_to_lookup = g_strdup_printf("%d", user->id); - } else { - /* Looking up buddy not on blist. Lookup by whatever user entered. */ - user_to_lookup = g_strdup(username); - } - - /* Pass the username to msim_get_info_cb(), because since we lookup - * by userid, the userinfo message will only contain the uid (not - * the username) but it would be useful to display the username too. - */ - user_msg = msim_msg_new( - "user", MSIM_TYPE_STRING, g_strdup(username), - NULL); - purple_debug_info("msim", "msim_get_info, setting up lookup, user=%s\n", username); - - msim_lookup_user(session, user_to_lookup, msim_get_info_cb, user_msg); - - g_free(user_to_lookup); -} - -/** Set your status - callback for when user manually sets it. */ -void -msim_set_status(PurpleAccount *account, PurpleStatus *status) -{ - PurpleStatusType *type; - PurplePresence *pres; - MsimSession *session; - guint status_code; - const gchar *message; - gchar *stripped; - gchar *unrecognized_msg; - - session = (MsimSession *)account->gc->proto_data; - - g_return_if_fail(MSIM_SESSION_VALID(session)); - - type = purple_status_get_type(status); - pres = purple_status_get_presence(status); - - switch (purple_status_type_get_primitive(type)) { - case PURPLE_STATUS_AVAILABLE: - purple_debug_info("msim", "msim_set_status: available (%d->%d)\n", PURPLE_STATUS_AVAILABLE, - MSIM_STATUS_CODE_ONLINE); - status_code = MSIM_STATUS_CODE_ONLINE; - break; - - case PURPLE_STATUS_INVISIBLE: - purple_debug_info("msim", "msim_set_status: invisible (%d->%d)\n", PURPLE_STATUS_INVISIBLE, - MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN); - status_code = MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN; - break; - - case PURPLE_STATUS_AWAY: - purple_debug_info("msim", "msim_set_status: away (%d->%d)\n", PURPLE_STATUS_AWAY, - MSIM_STATUS_CODE_AWAY); - status_code = MSIM_STATUS_CODE_AWAY; - break; - - default: - purple_debug_info("msim", "msim_set_status: unknown " - "status interpreting as online"); - status_code = MSIM_STATUS_CODE_ONLINE; - - unrecognized_msg = g_strdup_printf("msim_set_status, unrecognized status type: %d\n", - purple_status_type_get_primitive(type)); - msim_unrecognized(session, NULL, unrecognized_msg); - g_free(unrecognized_msg); - - break; - } - - message = purple_status_get_attr_string(status, "message"); - - /* Status strings are plain text. */ - if (message != NULL) - stripped = purple_markup_strip_html(message); - else - stripped = g_strdup(""); - - msim_set_status_code(session, status_code, stripped); - - /* If we should be idle, set that status. Time is irrelevant here. */ - if (purple_presence_is_idle(pres) && status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN) - msim_set_idle(account->gc, 1); - -} - -/** Go idle. */ -void -msim_set_idle(PurpleConnection *gc, int time) -{ - MsimSession *session; - PurpleStatus *status; - - g_return_if_fail(gc != NULL); - - session = (MsimSession *)gc->proto_data; - - g_return_if_fail(MSIM_SESSION_VALID(session)); - - status = purple_account_get_active_status(session->account); - - if (time == 0) { - /* Going back from idle. In msim, idle is mutually exclusive - * from the other states (you can only be away or idle, but not - * both, for example), so by going non-idle I go back to what - * libpurple says I should be. - */ - msim_set_status(session->account, status); - } else { - const gchar *message; - gchar *stripped; - - /* Set the idle message to the status message from the real - * current status. - */ - message = purple_status_get_attr_string(status, "message"); - if (message != NULL) - stripped = purple_markup_strip_html(message); - else - stripped = g_strdup(""); - - /* msim doesn't support idle time, so just go idle */ - msim_set_status_code(session, MSIM_STATUS_CODE_IDLE, stripped); - } -} - -/** Set status using an MSIM_STATUS_CODE_* value. - * @param status_code An MSIM_STATUS_CODE_* value. - * @param statstring Status string, must be a dynamic string (will be freed by msim_send). - */ -static void -msim_set_status_code(MsimSession *session, guint status_code, gchar *statstring) -{ - g_return_if_fail(MSIM_SESSION_VALID(session)); - g_return_if_fail(statstring != NULL); - - purple_debug_info("msim", "msim_set_status_code: going to set status to code=%d,str=%s\n", - status_code, statstring); - - if (!msim_send(session, - "status", MSIM_TYPE_INTEGER, status_code, - "sesskey", MSIM_TYPE_INTEGER, session->sesskey, - "statstring", MSIM_TYPE_STRING, statstring, - "locstring", MSIM_TYPE_STRING, g_strdup(""), - NULL)) - { - purple_debug_info("msim", "msim_set_status: failed to set status\n"); - } - -} - -/** After a uid is resolved to username, tag it with the username and submit for processing. - * - * @param session - * @param userinfo Response messsage to resolving request. - * @param data MsimMessage *, the message to attach information to. - */ -static void -msim_incoming_resolved(MsimSession *session, MsimMessage *userinfo, - gpointer data) -{ - gchar *username; - MsimMessage *msg, *body; - - g_return_if_fail(MSIM_SESSION_VALID(session)); - g_return_if_fail(userinfo != NULL); - - body = msim_msg_get_dictionary(userinfo, "body"); - g_return_if_fail(body != NULL); - - username = msim_msg_get_string(body, "UserName"); - g_return_if_fail(username != NULL); - /* Note: username will be owned by 'msg' below. */ - - msg = (MsimMessage *)data; - g_return_if_fail(msg != NULL); - - /* TODO: more elegant solution than below. attach whole message? */ - /* Special elements name beginning with '_', we'll use internally within the - * program (did not come directly from the wire). */ - msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, username); /* This makes 'msg' the owner of 'username' */ - - /* TODO: attach more useful information, like ImageURL */ - - msim_process(session, msg); - - /* TODO: Free copy cloned from msim_preprocess_incoming(). */ - /* msim_msg_free(msg); */ - msim_msg_free(body); -} - -/* Lookup a username by userid, from buddy list. - * - * @param wanted_uid - * - * @return Username of wanted_uid, if on blist, or NULL. - * This is a static string, so don't free it. Copy it if needed. - * - */ -static const gchar * -msim_uid2username_from_blist(PurpleAccount *account, guint wanted_uid) -{ - GSList *buddies, *cur; - const gchar *ret; - - buddies = purple_find_buddies(account, NULL); - - if (!buddies) - { - purple_debug_info("msim", "msim_uid2username_from_blist: no buddies?\n"); - return NULL; - } - - ret = NULL; - - for (cur = buddies; cur != NULL; cur = g_slist_next(cur)) - { - PurpleBuddy *buddy; - guint uid; - const gchar *name; - - /* See finch/gnthistory.c */ - buddy = cur->data; - - uid = purple_blist_node_get_int(&buddy->node, "UserID"); - name = purple_buddy_get_name(buddy); - - if (uid == wanted_uid) - { - ret = name; - break; - } - } - - g_slist_free(buddies); - return ret; -} - -/** Preprocess incoming messages, resolving as needed, calling msim_process() when ready to process. - * - * @param session - * @param msg MsimMessage *, freed by caller. - */ -static gboolean -msim_preprocess_incoming(MsimSession *session, MsimMessage *msg) +msim_is_username_set(MsimSession *session, MsimMessage *msg) { g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); g_return_val_if_fail(msg != NULL, FALSE); - - if (msim_msg_get(msg, "bm") && msim_msg_get(msg, "f")) { - guint uid; - const gchar *username; - - /* 'f' = userid message is from, in buddy messages */ - uid = msim_msg_get_integer(msg, "f"); - - username = msim_uid2username_from_blist(session->account, uid); - - if (username) { - /* Know username already, use it. */ - purple_debug_info("msim", "msim_preprocess_incoming: tagging with _username=%s\n", - username); - msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username)); - return msim_process(session, msg); - - } else { - gchar *from; - - /* Send lookup request. */ - /* XXX: where is msim_msg_get_string() freed? make _strdup and _nonstrdup. */ - purple_debug_info("msim", "msim_incoming: sending lookup, setting up callback\n"); - from = msim_msg_get_string(msg, "f"); - msim_lookup_user(session, from, msim_incoming_resolved, msim_msg_clone(msg)); - g_free(from); - - /* indeterminate */ - return TRUE; - } - } else { - /* Nothing to resolve - send directly to processing. */ - return msim_process(session, msg); + g_return_val_if_fail(session->gc != NULL, FALSE); + + session->sesskey = msim_msg_get_integer(msg, "sesskey"); + purple_debug_info("msim", "SESSKEY=<%d>\n", session->sesskey); + + /* What is proof? Used to be uid, but now is 52 base64'd bytes... */ + + /* Comes with: proof,profileid,userid,uniquenick -- all same values + * some of the time, but can vary. This is our own user ID. */ + session->userid = msim_msg_get_integer(msg, "userid"); + + /* Save uid to account so this account can be looked up by uid. */ + purple_account_set_int(session->account, "uid", session->userid); + + /* Not sure what profileid is used for. */ + if (msim_msg_get_integer(msg, "profileid") != session->userid) { + msim_unrecognized(session, msg, + "Profile ID didn't match user ID, don't know why"); } + + /* We now know are our own username, only after we're logged in.. + * which is weird, but happens because you login with your email + * address and not username. Will be freed in msim_session_destroy(). */ + session->username = msim_msg_get_string(msg, "uniquenick"); + + /* If user lacks a username, help them get one. */ + if (msim_msg_get_integer(msg, "uniquenick") == session->userid) { + purple_debug_info("msim_is_username_set", "no username is set\n"); + purple_request_yes_no(session->gc, + _("MySpaceIM - No Username Set"), + _("You appear to have no MySpace username."), + _("Would you like to set one now? (Note: THIS CANNOT BE CHANGED!)"), + 0, + session->account, + NULL, + NULL, + session->gc, + G_CALLBACK(msim_set_username_cb), + G_CALLBACK(msim_do_not_set_username_cb)); + purple_debug_info("msim_is_username_set","'username not set' alert prompted\n"); + return FALSE; + } + return TRUE; } #ifdef MSIM_USE_KEEPALIVE -/** Check if the connection is still alive, based on last communication. */ +/** + * Check if the connection is still alive, based on last communication. + */ static gboolean msim_check_alive(gpointer data) { @@ -1348,7 +832,9 @@ } #endif -/** Handle mail reply checks. */ +/** + * Handle mail reply checks. + */ static void msim_check_inbox_cb(MsimSession *session, MsimMessage *reply, gpointer data) { @@ -1358,7 +844,7 @@ const gchar *froms[5], *tos[5], *urls[5], *subjects[5]; /* Information for each new inbox message type. */ - static struct + static struct { const gchar *key; guint bit; @@ -1395,7 +881,7 @@ for (i = 0; i < sizeof(message_types) / sizeof(message_types[0]); ++i) { const gchar *key; guint bit; - + key = message_types[i].key; bit = message_types[i].bit; @@ -1433,7 +919,7 @@ purple_notify_emails(session->gc, /* handle */ n, /* count */ TRUE, /* detailed */ - subjects, froms, tos, urls, + subjects, froms, tos, urls, NULL, /* PurpleNotifyCloseCallback cb */ NULL); /* gpointer user_data */ @@ -1442,7 +928,9 @@ msim_msg_free(body); } -/* Send request to check if there is new mail. */ +/** + * Send request to check if there is new mail. + */ static gboolean msim_check_inbox(gpointer data) { @@ -1456,14 +944,14 @@ } purple_debug_info("msim", "msim_check_inbox: checking mail\n"); - g_return_val_if_fail(msim_send(session, + g_return_val_if_fail(msim_send(session, "persist", MSIM_TYPE_INTEGER, 1, "sesskey", MSIM_TYPE_INTEGER, session->sesskey, "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET, "dsn", MSIM_TYPE_INTEGER, MG_CHECK_MAIL_DSN, "lid", MSIM_TYPE_INTEGER, MG_CHECK_MAIL_LID, "uid", MSIM_TYPE_INTEGER, session->userid, - "rid", MSIM_TYPE_INTEGER, + "rid", MSIM_TYPE_INTEGER, msim_new_reply_callback(session, msim_check_inbox_cb, NULL), "body", MSIM_TYPE_STRING, g_strdup(""), NULL), TRUE); @@ -1472,1340 +960,9 @@ return TRUE; } -#ifdef MSIM_CHECK_NEWER_VERSION -/** Callback for when a currentversion.txt has been downloaded. */ -static void -msim_check_newer_version_cb(PurpleUtilFetchUrlData *url_data, - gpointer user_data, - const gchar *url_text, - gsize len, - const gchar *error_message) -{ - GKeyFile *keyfile; - GError *error; - GString *data; - gchar *newest_filever; - - if (!url_text) { - purple_debug_info("msim_check_newer_version_cb", - "got error: %s\n", error_message); - return; - } - - purple_debug_info("msim_check_newer_version_cb", - "url_text=%s\n", url_text ? url_text : "(NULL)"); - - /* Prepend [group] so that GKeyFile can parse it (requires a group). */ - data = g_string_new(url_text); - purple_debug_info("msim", "data=%s\n", data->str - ? data->str : "(NULL)"); - data = g_string_prepend(data, "[group]\n"); - - purple_debug_info("msim", "data=%s\n", data->str - ? data->str : "(NULL)"); - - /* url_text is variable=data\n...†*/ - - /* Check FILEVER, 1.0.716.0. 716 is build, MSIM_CLIENT_VERSION */ - /* New (english) version can be downloaded from SETUPURL+SETUPFILE */ - - error = NULL; - keyfile = g_key_file_new(); - - /* Default list seperator is ;, but currentversion.txt doesn't have - * these, so set to an unused character to avoid parsing problems. */ - g_key_file_set_list_separator(keyfile, '\0'); - - g_key_file_load_from_data(keyfile, data->str, data->len, - G_KEY_FILE_NONE, &error); - g_string_free(data, TRUE); - - if (error != NULL) { - purple_debug_info("msim_check_newer_version_cb", - "couldn't parse, error: %d %d %s\n", - error->domain, error->code, error->message); - g_error_free(error); - return; - } - - gchar **ks; - guint n; - ks = g_key_file_get_keys(keyfile, "group", &n, NULL); - purple_debug_info("msim", "n=%d\n", n); - guint i; - for (i = 0; ks[i] != NULL; ++i) - { - purple_debug_info("msim", "%d=%s\n", i, ks[i]); - } - - newest_filever = g_key_file_get_string(keyfile, "group", - "FILEVER", &error); - - purple_debug_info("msim_check_newer_version_cb", - "newest filever: %s\n", newest_filever ? - newest_filever : "(NULL)"); - if (error != NULL) { - purple_debug_info("msim_check_newer_version_cb", - "error: %d %d %s\n", - error->domain, error->code, error->message); - g_error_free(error); - } - - g_key_file_free(keyfile); - - exit(0); -} -#endif - -/** Called when the session key arrives to check whether the user - * has a username, and set one if desired. */ -static gboolean -msim_is_username_set(MsimSession *session, MsimMessage *msg) -{ - g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); - g_return_val_if_fail(msg != NULL, FALSE); - g_return_val_if_fail(session->gc != NULL, FALSE); - - session->sesskey = msim_msg_get_integer(msg, "sesskey"); - purple_debug_info("msim", "SESSKEY=<%d>\n", session->sesskey); - - /* What is proof? Used to be uid, but now is 52 base64'd bytes... */ - - /* Comes with: proof,profileid,userid,uniquenick -- all same values - * some of the time, but can vary. This is our own user ID. */ - session->userid = msim_msg_get_integer(msg, "userid"); - - /* Save uid to account so this account can be looked up by uid. */ - purple_account_set_int(session->account, "uid", session->userid); - - /* Not sure what profileid is used for. */ - if (msim_msg_get_integer(msg, "profileid") != session->userid) { - msim_unrecognized(session, msg, - "Profile ID didn't match user ID, don't know why"); - } - - /* We now know are our own username, only after we're logged in.. - * which is weird, but happens because you login with your email - * address and not username. Will be freed in msim_session_destroy(). */ - session->username = msim_msg_get_string(msg, "uniquenick"); - - /* If user lacks a username, help them get one. */ - if (msim_msg_get_integer(msg, "uniquenick") == session->userid) { - purple_debug_info("msim_is_username_set", "no username is set\n"); - purple_request_yes_no(session->gc, - _("MySpaceIM - No Username Set"), - _("You appear to have no MySpace username."), - _("Would you like to set one now? (Note: THIS CANNOT BE CHANGED!)"), - 0, - session->account, - NULL, - NULL, - session->gc, - G_CALLBACK(msim_set_username_cb), - G_CALLBACK(msim_do_not_set_username_cb)); - purple_debug_info("msim_is_username_set","'username not set' alert prompted\n"); - return FALSE; - } - return TRUE; -} - -/** Called after username is set, if necessary and we're open for business. */ -gboolean msim_we_are_logged_on(MsimSession *session) -{ - MsimMessage *body; - - g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); - - /* The session is now set up, ready to be connected. This emits the - * signedOn signal, so clients can now do anything with msimprpl, and - * we're ready for it (session key, userid, username all setup). */ - purple_connection_update_progress(session->gc, _("Connected"), 3, 4); - purple_connection_set_state(session->gc, PURPLE_CONNECTED); - - /* Set display name to username (otherwise will show email address) */ - purple_connection_set_display_name(session->gc, session->username); - - body = msim_msg_new( - "UserID", MSIM_TYPE_INTEGER, session->userid, - NULL); - - /* Request IM info about ourself. */ - msim_send(session, - "persist", MSIM_TYPE_STRING, g_strdup("persist"), - "sesskey", MSIM_TYPE_INTEGER, session->sesskey, - "dsn", MSIM_TYPE_INTEGER, MG_OWN_MYSPACE_INFO_DSN, - "uid", MSIM_TYPE_INTEGER, session->userid, - "lid", MSIM_TYPE_INTEGER, MG_OWN_MYSPACE_INFO_LID, - "rid", MSIM_TYPE_INTEGER, session->next_rid++, - "body", MSIM_TYPE_DICTIONARY, body, - NULL); - - /* Request MySpace info about ourself. */ - msim_send(session, - "persist", MSIM_TYPE_STRING, g_strdup("persist"), - "sesskey", MSIM_TYPE_INTEGER, session->sesskey, - "dsn", MSIM_TYPE_INTEGER, MG_OWN_IM_INFO_DSN, - "uid", MSIM_TYPE_INTEGER, session->userid, - "lid", MSIM_TYPE_INTEGER, MG_OWN_IM_INFO_LID, - "rid", MSIM_TYPE_INTEGER, session->next_rid++, - "body", MSIM_TYPE_STRING, g_strdup(""), - NULL); - - /* TODO: set options (persist cmd=514,dsn=1,lid=10) */ - /* TODO: set blocklist */ - - /* Notify servers of our current status. */ - purple_debug_info("msim", "msim_we_are_logged_on: notifying servers of status\n"); - msim_set_status(session->account, - purple_account_get_active_status(session->account)); - - /* TODO: setinfo */ - /* - body = msim_msg_new( - "TotalFriends", MSIM_TYPE_INTEGER, 666, - NULL); - msim_send(session, - "setinfo", MSIM_TYPE_BOOLEAN, TRUE, - "sesskey", MSIM_TYPE_INTEGER, session->sesskey, - "info", MSIM_TYPE_DICTIONARY, body, - NULL); - */ - - /* Disable due to problems with timeouts. TODO: fix. */ -#ifdef MSIM_USE_KEEPALIVE - purple_timeout_add(MSIM_KEEPALIVE_INTERVAL_CHECK, - (GSourceFunc)msim_check_alive, session); -#endif - - /* Check mail if they want to. */ - if (purple_account_get_check_mail(session->account)) { - session->inbox_handle = purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK, - (GSourceFunc)msim_check_inbox, session); - msim_check_inbox(session); - } - - msim_get_contact_list(session, MSIM_CONTACT_LIST_INITIAL_FRIENDS); - - return TRUE; -} - /** - * Process a message. - * - * @param session - * @param msg A message from the server, ready for processing (possibly with resolved username information attached). Caller frees. - * - * @return TRUE if successful. FALSE if processing failed. - */ -static gboolean -msim_process(MsimSession *session, MsimMessage *msg) -{ - g_return_val_if_fail(session != NULL, FALSE); - g_return_val_if_fail(msg != NULL, FALSE); - -#ifdef MSIM_DEBUG_MSG - msim_msg_dump("ready to process: %s\n", msg); -#endif - - if (msim_msg_get_integer(msg, "lc") == 1) { - return msim_login_challenge(session, msg); - } else if (msim_msg_get_integer(msg, "lc") == 2) { - /* return msim_we_are_logged_on(session, msg); */ - if (msim_is_username_set(session, msg)) { - return msim_we_are_logged_on(session); - } else { - /* No username is set... We'll wait for the callbacks to do their work */ - /* When they're all done, the last one will call msim_we_are_logged_on() and pick up where we left off */ - return FALSE; - } - } else if (msim_msg_get(msg, "bm")) { - return msim_incoming_bm(session, msg); - } else if (msim_msg_get(msg, "rid")) { - return msim_process_reply(session, msg); - } else if (msim_msg_get(msg, "error")) { - return msim_error(session, msg); - } else if (msim_msg_get(msg, "ka")) { - return TRUE; - } else { - msim_unrecognized(session, msg, "in msim_process"); - return FALSE; - } -} - -/** Process the initial server information from the server. */ -static gboolean -msim_process_server_info(MsimSession *session, MsimMessage *msg) -{ - MsimMessage *body; - - body = msim_msg_get_dictionary(msg, "body"); - g_return_val_if_fail(body != NULL, FALSE); - - /* Example body: -AdUnitRefreshInterval=10. -AlertPollInterval=360. -AllowChatRoomEmoticonSharing=False. -ChatRoomUserIDs=78744676;163733130;1300326231;123521495;142663391. -CurClientVersion=673. -EnableIMBrowse=True. -EnableIMStuffAvatars=False. -EnableIMStuffZaps=False. -MaxAddAllFriends=100. -MaxContacts=1000. -MinClientVersion=594. -MySpaceIM_ENGLISH=78744676. -MySpaceNowTimer=720. -PersistenceDataTimeout=900. -UseWebChallenge=1. -WebTicketGoHome=False - - Anything useful? TODO: use what is useful, and use it. -*/ - purple_debug_info("msim_process_server_info", - "maximum contacts: %d\n", - msim_msg_get_integer(body, "MaxContacts")); - - session->server_info = body; - /* session->server_info freed in msim_session_destroy */ - - return TRUE; -} - -/** Process a web challenge, used to login to the web site. */ -static gboolean -msim_web_challenge(MsimSession *session, MsimMessage *msg) -{ - /* TODO: web challenge, store token. #2659. */ - return FALSE; -} - -/** - * Process a persistance message reply from the server. - * - * @param session - * @param msg Message reply from server. - * - * @return TRUE if successful. - * - * msim_lookup_user sets callback for here - */ -static gboolean -msim_process_reply(MsimSession *session, MsimMessage *msg) -{ - MSIM_USER_LOOKUP_CB cb; - gpointer data; - guint rid, cmd, dsn, lid; - - g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); - g_return_val_if_fail(msg != NULL, FALSE); - - msim_store_user_info(session, msg, NULL); - - rid = msim_msg_get_integer(msg, "rid"); - cmd = msim_msg_get_integer(msg, "cmd"); - dsn = msim_msg_get_integer(msg, "dsn"); - lid = msim_msg_get_integer(msg, "lid"); - - /* Unsolicited messages */ - if (cmd == (MSIM_CMD_BIT_REPLY | MSIM_CMD_GET)) { - if (dsn == MG_SERVER_INFO_DSN && lid == MG_SERVER_INFO_LID) { - return msim_process_server_info(session, msg); - } else if (dsn == MG_WEB_CHALLENGE_DSN && lid == MG_WEB_CHALLENGE_LID) { - return msim_web_challenge(session, msg); - } - } - - /* If a callback is registered for this userid lookup, call it. */ - cb = g_hash_table_lookup(session->user_lookup_cb, GUINT_TO_POINTER(rid)); - data = g_hash_table_lookup(session->user_lookup_cb_data, GUINT_TO_POINTER(rid)); - - if (cb) { - purple_debug_info("msim", "msim_process_reply: calling callback now\n"); - msim_msg_dump("for msg=%s\n", msg); - /* Clone message, so that the callback 'cb' can use it (needs to free it also). */ - cb(session, msim_msg_clone(msg), data); - g_hash_table_remove(session->user_lookup_cb, GUINT_TO_POINTER(rid)); - g_hash_table_remove(session->user_lookup_cb_data, GUINT_TO_POINTER(rid)); - } else { - purple_debug_info("msim", - "msim_process_reply: no callback for rid %d\n", rid); - } - - return TRUE; -} - -/** - * Handle an error from the server. - * - * @param session - * @param msg The message. - * - * @return TRUE if successfully reported error. - */ -static gboolean -msim_error(MsimSession *session, MsimMessage *msg) -{ - gchar *errmsg, *full_errmsg; - guint err; - - g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); - g_return_val_if_fail(msg != NULL, FALSE); - - err = msim_msg_get_integer(msg, "err"); - errmsg = msim_msg_get_string(msg, "errmsg"); - - full_errmsg = g_strdup_printf(_("Protocol error, code %d: %s"), err, - errmsg ? errmsg : "no 'errmsg' given"); - - g_free(errmsg); - - purple_debug_info("msim", "msim_error (sesskey=%d): %s\n", - session->sesskey, full_errmsg); - - /* Destroy session if fatal. */ - if (msim_msg_get(msg, "fatal")) { - PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; - purple_debug_info("msim", "fatal error, closing\n"); - - switch (err) { - case MSIM_ERROR_INCORRECT_PASSWORD: /* Incorrect password */ - reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; - if (!purple_account_get_remember_password(session->account)) - purple_account_set_password(session->account, NULL); -#ifdef MSIM_MAX_PASSWORD_LENGTH - if (session->account->password && (strlen(session->account->password) > MSIM_MAX_PASSWORD_LENGTH)) { - gchar *suggestion; - - suggestion = g_strdup_printf(_("%s Your password is " - "%d characters, greater than the " - "expected maximum length of %d for " - "MySpaceIM. Please shorten your " - "password at http://profileedit.myspace.com/index.cfm?fuseaction=accountSettings.changePassword and try again."), - full_errmsg, (int) - strlen(session->account->password), - MSIM_MAX_PASSWORD_LENGTH); - - /* Replace full_errmsg. */ - g_free(full_errmsg); - full_errmsg = suggestion; - } -#endif - break; - case MSIM_ERROR_LOGGED_IN_ELSEWHERE: /* Logged in elsewhere */ - reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE; - if (!purple_account_get_remember_password(session->account)) - purple_account_set_password(session->account, NULL); - break; - } - purple_connection_error_reason (session->gc, reason, full_errmsg); - } else { - purple_notify_error(session->account, _("MySpaceIM Error"), full_errmsg, NULL); - } - - g_free(full_errmsg); - - return TRUE; -} - -/** - * Process incoming status messages. - * - * @param session - * @param msg Status update message. Caller frees. - * - * @return TRUE if successful. - */ -static gboolean -msim_incoming_status(MsimSession *session, MsimMessage *msg) -{ - PurpleBuddyList *blist; - MsimUser *user; - GList *list; - gchar *status_headline, *status_headline_escaped; - gint status_code, purple_status_code; - gchar *username; - gchar *unrecognized_msg; - - g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); - g_return_val_if_fail(msg != NULL, FALSE); - - msim_msg_dump("msim_status msg=%s\n", msg); - - /* Helpfully looked up by msim_incoming_resolve() for us. */ - username = msim_msg_get_string(msg, "_username"); - g_return_val_if_fail(username != NULL, FALSE); - - { - gchar *ss; - - ss = msim_msg_get_string(msg, "msg"); - purple_debug_info("msim", - "msim_status: updating status for <%s> to <%s>\n", - username, ss ? ss : "(NULL)"); - g_free(ss); - } - - /* Example fields: - * |s|0|ss|Offline - * |s|1|ss|:-)|ls||ip|0|p|0 - */ - list = msim_msg_get_list(msg, "msg"); - - status_code = msim_msg_get_integer_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE)); - purple_debug_info("msim", "msim_status: %s's status code = %d\n", username, status_code); - status_headline = msim_msg_get_string_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE)); - - blist = purple_get_blist(); - - /* Add buddy if not found. - * TODO: Could this be responsible for #3444? */ - user = msim_find_user(session, username); - if (!user) { - PurpleBuddy *buddy; - - purple_debug_info("msim", - "msim_status: making new buddy for %s\n", username); - buddy = purple_buddy_new(session->account, username, NULL); - purple_blist_add_buddy(buddy, NULL, NULL, NULL); - - user = msim_get_user_from_buddy(buddy); - user->id = msim_msg_get_integer(msg, "f"); - - /* Keep track of the user ID across sessions */ - purple_blist_node_set_int(&buddy->node, "UserID", user->id); - - msim_store_user_info(session, msg, NULL); - } else { - purple_debug_info("msim", "msim_status: found buddy %s\n", username); - } - - if (status_headline && strcmp(status_headline, "") != 0) { - /* The status headline is plaintext, but libpurple treats it as HTML, - * so escape any HTML characters to their entity equivalents. */ - status_headline_escaped = g_markup_escape_text(status_headline, strlen(status_headline)); - } else { - status_headline_escaped = NULL; - } - - g_free(status_headline); - - if (user->headline) - g_free(user->headline); - - /* don't copy; let the MsimUser own the headline, memory-wise */ - user->headline = status_headline_escaped; - - /* Set user status */ - switch (status_code) { - case MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN: - purple_status_code = PURPLE_STATUS_OFFLINE; - break; - - case MSIM_STATUS_CODE_ONLINE: - purple_status_code = PURPLE_STATUS_AVAILABLE; - break; - - case MSIM_STATUS_CODE_AWAY: - purple_status_code = PURPLE_STATUS_AWAY; - break; - - case MSIM_STATUS_CODE_IDLE: - /* Treat idle as an available status. */ - purple_status_code = PURPLE_STATUS_AVAILABLE; - break; - - default: - purple_debug_info("msim", "msim_incoming_status for %s, unknown status code %d, treating as available\n", - username, status_code); - purple_status_code = PURPLE_STATUS_AVAILABLE; - - unrecognized_msg = g_strdup_printf("msim_incoming_status, unrecognized status code: %d\n", - status_code); - msim_unrecognized(session, NULL, unrecognized_msg); - g_free(unrecognized_msg); - - } - - purple_prpl_got_user_status(session->account, username, purple_primitive_get_id_from_type(purple_status_code), NULL); - - if (status_code == MSIM_STATUS_CODE_IDLE) { - purple_debug_info("msim", "msim_status: got idle: %s\n", username); - purple_prpl_got_user_idle(session->account, username, TRUE, time(NULL)); - } else { - /* All other statuses indicate going back to non-idle. */ - purple_prpl_got_user_idle(session->account, username, FALSE, time(NULL)); - } - -#ifdef MSIM_SEND_CLIENT_VERSION - if (status_code == MSIM_STATUS_CODE_ONLINE) { - /* Secretly whisper to unofficial clients our own version as they come online */ - msim_send_unofficial_client(session, username); - } -#endif - - if (status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN) { - /* Get information when they come online. - * TODO: periodically refresh? - */ - purple_debug_info("msim_incoming_status", "%s came online, looking up\n", username); - msim_lookup_user(session, username, NULL, NULL); - } - - g_free(username); - msim_msg_list_free(list); - - return TRUE; -} - -/** Add a buddy to user's buddy list. */ -void -msim_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) -{ - MsimSession *session; - MsimMessage *msg; - MsimMessage *msg_persist; - MsimMessage *body; - - session = (MsimSession *)gc->proto_data; - purple_debug_info("msim", "msim_add_buddy: want to add %s to %s\n", - buddy->name, (group && group->name) ? group->name : "(no group)"); - - msg = msim_msg_new( - "addbuddy", MSIM_TYPE_BOOLEAN, TRUE, - "sesskey", MSIM_TYPE_INTEGER, session->sesskey, - /* "newprofileid" will be inserted here with uid. */ - "reason", MSIM_TYPE_STRING, g_strdup(""), - NULL); - - if (!msim_postprocess_outgoing(session, msg, buddy->name, "newprofileid", "reason")) { - purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("'addbuddy' command failed.")); - msim_msg_free(msg); - return; - } - msim_msg_free(msg); - - /* TODO: if addbuddy fails ('error' message is returned), delete added buddy from - * buddy list since Purple adds it locally. */ - - body = msim_msg_new( - "ContactID", MSIM_TYPE_STRING, g_strdup("<uid>"), - "GroupName", MSIM_TYPE_STRING, g_strdup(group->name), - "Position", MSIM_TYPE_INTEGER, 1000, - "Visibility", MSIM_TYPE_INTEGER, 1, - "NickName", MSIM_TYPE_STRING, g_strdup(""), - "NameSelect", MSIM_TYPE_INTEGER, 0, - NULL); - - /* TODO: Update blocklist. */ - - msg_persist = msim_msg_new( - "persist", MSIM_TYPE_INTEGER, 1, - "sesskey", MSIM_TYPE_INTEGER, session->sesskey, - "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_PUT, - "dsn", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_DSN, - "uid", MSIM_TYPE_INTEGER, session->userid, - "lid", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_LID, - /* TODO: Use msim_new_reply_callback to get rid. */ - "rid", MSIM_TYPE_INTEGER, session->next_rid++, - "body", MSIM_TYPE_DICTIONARY, body, - NULL); - - if (!msim_postprocess_outgoing(session, msg_persist, buddy->name, "body", NULL)) - { - purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("persist command failed")); - msim_msg_free(msg_persist); - return; - } - msim_msg_free(msg_persist); - -} - -/** Perform actual postprocessing on a message, adding userid as specified. - * - * @param msg The message to postprocess. - * @param uid_before Name of field where to insert new field before, or NULL for end. - * @param uid_field_name Name of field to add uid to. - * @param uid The userid to insert. - * - * If the field named by uid_field_name already exists, then its string contents will - * be used for the field, except "<uid>" will be replaced by the userid. - * - * If the field named by uid_field_name does not exist, it will be added before the - * field named by uid_before, as an integer, with the userid. - * - * Does not handle sending, or scheduling userid lookup. For that, see msim_postprocess_outgoing(). - */ -static MsimMessage * -msim_do_postprocessing(MsimMessage *msg, const gchar *uid_before, - const gchar *uid_field_name, guint uid) -{ - MsimMessageElement *elem; - msim_msg_dump("msim_do_postprocessing msg: %s\n", msg); - - /* First, check - if the field already exists, replace <uid> within it */ - if ((elem = msim_msg_get(msg, uid_field_name)) != NULL) { - gchar *fmt_string; - gchar *uid_str, *new_str; - - /* Get the packed element, flattening it. This allows <uid> to be - * replaced within nested data structures, since the replacement is done - * on the linear, packed data, not on a complicated data structure. - * - * For example, if the field was originally a dictionary or a list, you - * would have to iterate over all the items in it to see what needs to - * be replaced. But by packing it first, the <uid> marker is easily replaced - * just by a string replacement. - */ - fmt_string = msim_msg_pack_element_data(elem); - - uid_str = g_strdup_printf("%d", uid); - new_str = purple_strreplace(fmt_string, "<uid>", uid_str); - g_free(uid_str); - g_free(fmt_string); - - /* Free the old element data */ - msim_msg_free_element_data(elem->data); - - /* Replace it with our new data */ - elem->data = new_str; - elem->type = MSIM_TYPE_RAW; - - } else { - /* Otherwise, insert new field into outgoing message. */ - msg = msim_msg_insert_before(msg, uid_before, uid_field_name, MSIM_TYPE_INTEGER, GUINT_TO_POINTER(uid)); - } - - msim_msg_dump("msim_postprocess_outgoing_cb: postprocessed msg=%s\n", msg); - - return msg; -} - -/** Callback for msim_postprocess_outgoing() to add a userid to a message, and send it (once receiving userid). - * - * @param session - * @param userinfo The user information reply message, containing the user ID - * @param data The message to postprocess and send. - * - * The data message should contain these fields: - * - * _uid_field_name: string, name of field to add with userid from userinfo message - * _uid_before: string, name of field before field to insert, or NULL for end - * - * -*/ -static void -msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, - gpointer data) -{ - gchar *uid_field_name, *uid_before, *username; - guint uid; - MsimMessage *msg, *body; - - msg = (MsimMessage *)data; - - msim_msg_dump("msim_postprocess_outgoing_cb() got msg=%s\n", msg); - - /* Obtain userid from userinfo message. */ - body = msim_msg_get_dictionary(userinfo, "body"); - g_return_if_fail(body != NULL); - - uid = msim_msg_get_integer(body, "UserID"); - msim_msg_free(body); - - username = msim_msg_get_string(msg, "_username"); - - if (!uid) { - gchar *msg; - - msg = g_strdup_printf(_("No such user: %s"), username); - if (!purple_conv_present_error(username, session->account, msg)) { - purple_notify_error(NULL, NULL, _("User lookup"), msg); - } - - g_free(msg); - g_free(username); - /* TODO: free - * msim_msg_free(msg); - */ - return; - } - - uid_field_name = msim_msg_get_string(msg, "_uid_field_name"); - uid_before = msim_msg_get_string(msg, "_uid_before"); - - msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid); - - /* Send */ - if (!msim_msg_send(session, msg)) { - msim_msg_dump("msim_postprocess_outgoing_cb: sending failed for message: %s\n", msg); - } - - - /* Free field names AFTER sending message, because MsimMessage does NOT copy - * field names - instead, treats them as static strings (which they usually are). - */ - g_free(uid_field_name); - g_free(uid_before); - g_free(username); - /* TODO: free - * msim_msg_free(msg); - */ -} - -/** Postprocess and send a message. - * - * @param session - * @param msg Message to postprocess. Will NOT be freed. - * @param username Username to resolve. Assumed to be a static string (will not be freed or copied). - * @param uid_field_name Name of new field to add, containing uid of username. Static string. - * @param uid_before Name of existing field to insert username field before. Static string. - * - * @return TRUE if successful. - */ -gboolean -msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, - const gchar *username, const gchar *uid_field_name, - const gchar *uid_before) -{ - PurpleBuddy *buddy; - guint uid; - gboolean rc; - - g_return_val_if_fail(msg != NULL, FALSE); - - /* Store information for msim_postprocess_outgoing_cb(). */ - msim_msg_dump("msim_postprocess_outgoing: msg before=%s\n", msg); - msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username)); - msg = msim_msg_append(msg, "_uid_field_name", MSIM_TYPE_STRING, g_strdup(uid_field_name)); - msg = msim_msg_append(msg, "_uid_before", MSIM_TYPE_STRING, g_strdup(uid_before)); - - /* First, try the most obvious. If numeric userid is given, use that directly. */ - if (msim_is_userid(username)) { - uid = atol(username); - } else { - /* Next, see if on buddy list and know uid. */ - buddy = purple_find_buddy(session->account, username); - if (buddy) { - uid = purple_blist_node_get_int(&buddy->node, "UserID"); - } else { - uid = 0; - } - - if (!buddy || !uid) { - /* Don't have uid offhand - need to ask for it, and wait until hear back before sending. */ - purple_debug_info("msim", ">>> msim_postprocess_outgoing: couldn't find username %s in blist\n", - username ? username : "(NULL)"); - msim_msg_dump("msim_postprocess_outgoing - scheduling lookup, msg=%s\n", msg); - /* TODO: where is cloned message freed? Should be in _cb. */ - msim_lookup_user(session, username, msim_postprocess_outgoing_cb, msim_msg_clone(msg)); - return TRUE; /* not sure of status yet - haven't sent! */ - } - } - - /* Already have uid, postprocess and send msg immediately. */ - purple_debug_info("msim", "msim_postprocess_outgoing: found username %s has uid %d\n", - username ? username : "(NULL)", uid); - - msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid); - - msim_msg_dump("msim_postprocess_outgoing: msg after (uid immediate)=%s\n", msg); - - rc = msim_msg_send(session, msg); - - /* TODO: free - * msim_msg_free(msg); - */ - - return rc; -} - -/** Remove a buddy from the user's buddy list. */ -void -msim_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) -{ - MsimSession *session; - MsimMessage *delbuddy_msg; - MsimMessage *persist_msg; - MsimMessage *blocklist_msg; - GList *blocklist_updates; - - session = (MsimSession *)gc->proto_data; - - delbuddy_msg = msim_msg_new( - "delbuddy", MSIM_TYPE_BOOLEAN, TRUE, - "sesskey", MSIM_TYPE_INTEGER, session->sesskey, - /* 'delprofileid' with uid will be inserted here. */ - NULL); - - if (!msim_postprocess_outgoing(session, delbuddy_msg, buddy->name, "delprofileid", NULL)) { - purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("'delbuddy' command failed")); - msim_msg_free(delbuddy_msg); - return; - } - msim_msg_free(delbuddy_msg); - - persist_msg = msim_msg_new( - "persist", MSIM_TYPE_INTEGER, 1, - "sesskey", MSIM_TYPE_INTEGER, session->sesskey, - "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_DELETE, - "dsn", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_DSN, - "lid", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_LID, - "uid", MSIM_TYPE_INTEGER, session->userid, - "rid", MSIM_TYPE_INTEGER, session->next_rid++, - /* <uid> will be replaced by postprocessing */ - "body", MSIM_TYPE_STRING, g_strdup("ContactID=<uid>"), - NULL); - - if (!msim_postprocess_outgoing(session, persist_msg, buddy->name, "body", NULL)) { - purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("persist command failed")); - msim_msg_free(persist_msg); - return; - } - msim_msg_free(persist_msg); - - blocklist_updates = NULL; - blocklist_updates = g_list_prepend(blocklist_updates, "a-"); - blocklist_updates = g_list_prepend(blocklist_updates, "<uid>"); - blocklist_updates = g_list_prepend(blocklist_updates, "b-"); - blocklist_updates = g_list_prepend(blocklist_updates, "<uid>"); - blocklist_updates = g_list_reverse(blocklist_updates); - - blocklist_msg = msim_msg_new( - "blocklist", MSIM_TYPE_BOOLEAN, TRUE, - "sesskey", MSIM_TYPE_INTEGER, session->sesskey, - /* TODO: MsimMessage lists. Currently <uid> isn't replaced in lists. */ - /* "idlist", MSIM_TYPE_STRING, g_strdup("a-|<uid>|b-|<uid>"), */ - "idlist", MSIM_TYPE_LIST, blocklist_updates, - NULL); - - if (!msim_postprocess_outgoing(session, blocklist_msg, buddy->name, "idlist", NULL)) { - purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("blocklist command failed")); - msim_msg_free(blocklist_msg); - return; - } - msim_msg_free(blocklist_msg); -} - -/** - * Returns a string of a username in canonical form. Basically removes all the - * spaces, lowercases the string, and looks up user IDs to usernames. - * Normalizing tom, TOM, Tom, and 6221 wil all return 'tom'. - * - * Borrowed this code from oscar_normalize. Added checking for - * "if userid, get name before normalizing" - */ -const char *msim_normalize(const PurpleAccount *account, const char *str) { - static char normalized[BUF_LEN]; - char *tmp1, *tmp2; - int i, j; - guint id; - - g_return_val_if_fail(str != NULL, NULL); - - if (msim_is_userid(str)) { - /* Have user ID, we need to get their username first :) */ - const char *username; - - /* If the account does not exist, we can't look up the user. */ - if (!account || !account->gc) - return str; - - id = atol(str); - username = msim_uid2username_from_blist((PurpleAccount *)account, id); - if (!username) { - /* Not in buddy list... scheisse... TODO: Manual Lookup! Bug #4631 */ - /* Note: manual lookup using msim_lookup_user() is a problem inside - * msim_normalize(), because msim_lookup_user() calls a callback function - * when the user information has been looked up, but msim_normalize() expects - * the result immediately. */ - strncpy(normalized, str, BUF_LEN); - } else { - strncpy(normalized, username, BUF_LEN); - } - } else { - /* Have username. */ - strncpy(normalized, str, BUF_LEN); - } - - /* Strip spaces. */ - for (i=0, j=0; normalized[j]; i++, j++) { - while (normalized[j] == ' ') - j++; - normalized[i] = normalized[j]; - } - normalized[i] = '\0'; - - /* Lowercase and perform UTF-8 normalization. */ - tmp1 = g_utf8_strdown(normalized, -1); - tmp2 = g_utf8_normalize(tmp1, -1, G_NORMALIZE_DEFAULT); - g_snprintf(normalized, sizeof(normalized), "%s", tmp2); - g_free(tmp2); - g_free(tmp1); - - /* TODO: re-add caps and spacing back to what the user wanted. - * User can format their own names, for example 'msimprpl' is shown - * as 'MsIm PrPl' in the official client. - * - * TODO: file a ticket to add this enhancement. - */ - - return normalized; -} - -static GHashTable * -msim_get_account_text_table(PurpleAccount *unused) -{ - GHashTable *table; - - table = g_hash_table_new(g_str_hash, g_str_equal); - - g_hash_table_insert(table, "login_label", (gpointer)_("Email Address...")); - - return table; -} - -/** Return whether the buddy can be messaged while offline. - * - * The protocol supports offline messages in just the same way as online - * messages. - */ -gboolean -msim_offline_message(const PurpleBuddy *buddy) -{ - return TRUE; -} - -/** - * Callback when input available. - * - * @param gc_uncasted A PurpleConnection pointer. - * @param source File descriptor. - * @param cond PURPLE_INPUT_READ - * - * Reads the input, and calls msim_preprocess_incoming() to handle it. - */ -static void -msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond) -{ - PurpleConnection *gc; - PurpleAccount *account; - MsimSession *session; - gchar *end; - int n; - - g_return_if_fail(gc_uncasted != NULL); - g_return_if_fail(source >= 0); /* Note: 0 is a valid fd */ - - gc = (PurpleConnection *)(gc_uncasted); - account = purple_connection_get_account(gc); - session = gc->proto_data; - - /* libpurple/eventloop.h only defines these two */ - if (cond != PURPLE_INPUT_READ && cond != PURPLE_INPUT_WRITE) { - purple_debug_info("msim_input_cb", "unknown condition=%d\n", cond); - purple_connection_error_reason (gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Invalid input condition")); - return; - } - - g_return_if_fail(cond == PURPLE_INPUT_READ); - g_return_if_fail(MSIM_SESSION_VALID(session)); - - /* Mark down that we got data, so we don't timeout. */ - session->last_comm = time(NULL); - - /* If approaching end of buffer, reallocate some more memory. */ - if (session->rxsize < session->rxoff + MSIM_READ_BUF_SIZE) { - purple_debug_info("msim", - "msim_input_cb: %d-byte read buffer full, rxoff=%d, " "growing by %d bytes\n", - session->rxsize, session->rxoff, MSIM_READ_BUF_SIZE); - session->rxsize += MSIM_READ_BUF_SIZE; - session->rxbuf = g_realloc(session->rxbuf, session->rxsize); - - return; - } - - purple_debug_info("msim", "dynamic buffer at %d (max %d), reading up to %d\n", - session->rxoff, session->rxsize, - MSIM_READ_BUF_SIZE - session->rxoff - 1); - - /* Read into buffer. On Win32, need recv() not read(). session->fd also holds - * the file descriptor, but it sometimes differs from the 'source' parameter. - */ - n = recv(session->fd, - session->rxbuf + session->rxoff, - session->rxsize - session->rxoff - 1, 0); - - if (n < 0 && errno == EAGAIN) { - return; - } else if (n < 0) { - purple_debug_error("msim", "msim_input_cb: read error, ret=%d, " - "error=%s, source=%d, fd=%d (%X))\n", - n, g_strerror(errno), source, session->fd, session->fd); - purple_connection_error_reason (gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Read error")); - return; - } else if (n == 0) { - purple_debug_info("msim", "msim_input_cb: server disconnected\n"); - purple_connection_error_reason (gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Server has disconnected")); - return; - } - - if (n + session->rxoff > session->rxsize) { - purple_debug_info("msim_input_cb", "received %d bytes, pushing rxoff to %d, over buffer size of %d\n", - n, n + session->rxoff, session->rxsize); - purple_connection_error_reason (gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Read buffer full (2)")); - return; - } - - /* Null terminate */ - purple_debug_info("msim", "msim_input_cb: going to null terminate " - "at n=%d\n", n); - session->rxbuf[session->rxoff + n] = 0; - -#ifdef MSIM_CHECK_EMBEDDED_NULLS - /* Check for embedded NULs. I don't handle them, and they shouldn't occur. */ - if (strlen(session->rxbuf + session->rxoff) != n) { - /* Occurs after login, but it is not a null byte. */ - purple_debug_info("msim", "msim_input_cb: strlen=%d, but read %d bytes" - "--null byte encountered?\n", - strlen(session->rxbuf + session->rxoff), n); - /*purple_connection_error_reason (gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - "Invalid message - null byte on input"); */ - return; - } -#endif - - session->rxoff += n; - purple_debug_info("msim", "msim_input_cb: read=%d\n", n); - -#ifdef MSIM_DEBUG_RXBUF - purple_debug_info("msim", "buf=<%s>\n", session->rxbuf); -#endif - - /* Look for \\final\\ end markers. If found, process message. */ - while((end = strstr(session->rxbuf, MSIM_FINAL_STRING))) { - MsimMessage *msg; - -#ifdef MSIM_DEBUG_RXBUF - purple_debug_info("msim", "in loop: buf=<%s>\n", session->rxbuf); -#endif - *end = 0; - msg = msim_parse(g_strdup(session->rxbuf)); - if (!msg) { - purple_debug_info("msim", "msim_input_cb: couldn't parse rxbuf\n"); - purple_connection_error_reason (gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Unparseable message")); - break; - } else { - /* Process message and then free it (processing function should - * clone message if it wants to keep it afterwards.) */ - if (!msim_preprocess_incoming(session, msg)) { - msim_msg_dump("msim_input_cb: preprocessing message failed on msg: %s\n", msg); - } - msim_msg_free(msg); - } - - /* Move remaining part of buffer to beginning. */ - session->rxoff -= strlen(session->rxbuf) + strlen(MSIM_FINAL_STRING); - memmove(session->rxbuf, end + strlen(MSIM_FINAL_STRING), - session->rxsize - (end + strlen(MSIM_FINAL_STRING) - session->rxbuf)); - - /* Clear end of buffer - * memset(end, 0, MSIM_READ_BUF_SIZE - (end - session->rxbuf)); - */ - } -} - -/* Setup a callback, to be called when a reply is received with the returned rid. - * - * @param cb The callback, an MSIM_USER_LOOKUP_CB. - * @param data Arbitrary user data to be passed to callback (probably an MsimMessage *). - * - * @return The request/reply ID, used to link replies with requests, or -1. - * Put the rid in your request, 'rid' field. - * - * TODO: Make more generic and more specific: - * 1) MSIM_USER_LOOKUP_CB - make it for PERSIST_REPLY, not just user lookup - * 2) data - make it an MsimMessage? - */ -guint -msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb, - gpointer data) -{ - guint rid; - - g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); - - rid = session->next_rid++; - - g_hash_table_insert(session->user_lookup_cb, GUINT_TO_POINTER(rid), cb); - g_hash_table_insert(session->user_lookup_cb_data, GUINT_TO_POINTER(rid), data); - - return rid; -} - -/** - * Callback when connected. Sets up input handlers. - * - * @param data A PurpleConnection pointer. - * @param source File descriptor. - * @param error_message - */ -static void -msim_connect_cb(gpointer data, gint source, const gchar *error_message) -{ - PurpleConnection *gc; - MsimSession *session; - - g_return_if_fail(data != NULL); - - gc = (PurpleConnection *)data; - session = (MsimSession *)gc->proto_data; - - if (source < 0) { - purple_connection_error_reason (gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - g_strdup_printf(_("Couldn't connect to host: %s (%d)"), - error_message ? error_message : "no message given", - source)); - return; - } - - session->fd = source; - - gc->inpa = purple_input_add(source, PURPLE_INPUT_READ, msim_input_cb, gc); -} - - -/** - * Close the connection. - * - * @param gc The connection. - */ -void -msim_close(PurpleConnection *gc) -{ - MsimSession *session; - - if (gc == NULL) { - return; - } - - session = (MsimSession *)gc->proto_data; - if (session == NULL) - return; - - gc->proto_data = NULL; - - if (!MSIM_SESSION_VALID(session)) { - return; - } - - if (session->gc->inpa) { - purple_input_remove(session->gc->inpa); - } - - msim_session_destroy(session); -} - - -/** - * Obtain the status text for a buddy. - * - * @param buddy The buddy to obtain status text for. - * - * @return Status text, or NULL if error. Caller g_free()'s. - * - */ -char * -msim_status_text(PurpleBuddy *buddy) -{ - MsimSession *session; - MsimUser *user; - const gchar *display_name, *headline; - - g_return_val_if_fail(buddy != NULL, NULL); - - user = msim_get_user_from_buddy(buddy); - - session = (MsimSession *)buddy->account->gc->proto_data; - g_return_val_if_fail(MSIM_SESSION_VALID(session), NULL); - - display_name = headline = NULL; - - /* Retrieve display name and/or headline, depending on user preference. */ - if (purple_account_get_bool(session->account, "show_headline", TRUE)) { - headline = user->headline; - } - - if (purple_account_get_bool(session->account, "show_display_name", FALSE)) { - display_name = user->display_name; - } - - /* Return appropriate combination of display name and/or headline, or neither. */ - - if (display_name && headline) { - return g_strconcat(display_name, " ", headline, NULL); - } else if (display_name) { - return g_strdup(display_name); - } else if (headline) { - return g_strdup(headline); - } - - return NULL; -} - -/** - * Obtain the tooltip text for a buddy. - * - * @param buddy Buddy to obtain tooltip text on. - * @param user_info Variable modified to have the tooltip text. - * @param full TRUE if should obtain full tooltip text. - * - */ -void -msim_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, - gboolean full) -{ - MsimUser *user; - - g_return_if_fail(buddy != NULL); - g_return_if_fail(user_info != NULL); - - user = msim_get_user_from_buddy(buddy); - - if (PURPLE_BUDDY_IS_ONLINE(buddy)) { - MsimSession *session; - - session = (MsimSession *)buddy->account->gc->proto_data; - - g_return_if_fail(MSIM_SESSION_VALID(session)); - - /* TODO: if (full), do something different? */ - - /* TODO: request information? have to figure out how to do - * the asynchronous lookup like oscar does (tooltip shows - * 'retrieving...' if not yet available, then changes when it is). - * - * Right now, only show what we have on hand. - */ - - /* Show abbreviated user info. */ - msim_append_user_info(session, user_info, user, FALSE); - } -} - -/** Add contact from server to buddy list, after looking up username. - * Callback from msim_add_contact_from_server(). + * Add contact from server to buddy list, after looking up username. + * Callback from msim_add_contact_from_server(). * * @param data An MsimMessage * of the contact information. Will be freed. */ @@ -2880,11 +1037,12 @@ g_free(username); } -/** Add first ContactID in contact_info to buddy's list. Used to add - * server-side buddies to client-side list. +/** + * Add first ContactID in contact_info to buddy's list. Used to add + * server-side buddies to client-side list. * * @return TRUE if added. - * */ + */ static gboolean msim_add_contact_from_server(MsimSession *session, MsimMessage *contact_info) { @@ -2913,7 +1071,9 @@ return TRUE; } -/** Called when contact list is received from server. */ +/** + * Called when contact list is received from server. + */ static void msim_got_contact_list(MsimSession *session, MsimMessage *reply, gpointer user_data) { @@ -2961,7 +1121,7 @@ case MSIM_CONTACT_LIST_IMPORT_TOP_FRIENDS: /* TODO */ break; - + case MSIM_CONTACT_LIST_INITIAL_FRIENDS: /* Nothing */ break; @@ -2970,119 +1130,1734 @@ msim_msg_free(body); } -/* Get contact list, calling msim_got_contact_list() with what_to_do_after as user_data gpointer. */ +/** + * Get contact list, calling msim_got_contact_list() with + * what_to_do_after as user_data gpointer. + */ static gboolean msim_get_contact_list(MsimSession *session, int what_to_do_after) { - return msim_send(session, + return msim_send(session, "persist", MSIM_TYPE_INTEGER, 1, "sesskey", MSIM_TYPE_INTEGER, session->sesskey, "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET, "dsn", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_DSN, "lid", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_LID, "uid", MSIM_TYPE_INTEGER, session->userid, - "rid", MSIM_TYPE_INTEGER, - msim_new_reply_callback(session, msim_got_contact_list, GUINT_TO_POINTER(what_to_do_after)), + "rid", MSIM_TYPE_INTEGER, + msim_new_reply_callback(session, msim_got_contact_list, GUINT_TO_POINTER(what_to_do_after)), "body", MSIM_TYPE_STRING, g_strdup(""), NULL); } - -/** Called when friends have been imported to buddy list on server. */ -static void -msim_import_friends_cb(MsimSession *session, MsimMessage *reply, gpointer user_data) +/** Called after username is set, if necessary and we're open for business. */ +gboolean msim_we_are_logged_on(MsimSession *session) { MsimMessage *body; - gchar *completed; - msim_msg_dump("msim_import_friends_cb=%s", reply); - - /* Check if the friends were imported successfully. */ - body = msim_msg_get_dictionary(reply, "body"); - g_return_if_fail(body != NULL); - completed = msim_msg_get_string(body, "Completed"); + + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + + /* Set display name to username (otherwise will show email address) */ + purple_connection_set_display_name(session->gc, session->username); + + /* The session is now set up, ready to be connected. This emits the + * signedOn signal, so clients can now do anything with msimprpl, and + * we're ready for it (session key, userid, username all setup). */ + purple_connection_update_progress(session->gc, _("Connected"), 3, 4); + purple_connection_set_state(session->gc, PURPLE_CONNECTED); + + body = msim_msg_new( + "UserID", MSIM_TYPE_INTEGER, session->userid, + NULL); + + /* Request IM info about ourself. */ + msim_send(session, + "persist", MSIM_TYPE_STRING, g_strdup("persist"), + "sesskey", MSIM_TYPE_INTEGER, session->sesskey, + "dsn", MSIM_TYPE_INTEGER, MG_OWN_MYSPACE_INFO_DSN, + "uid", MSIM_TYPE_INTEGER, session->userid, + "lid", MSIM_TYPE_INTEGER, MG_OWN_MYSPACE_INFO_LID, + "rid", MSIM_TYPE_INTEGER, session->next_rid++, + "body", MSIM_TYPE_DICTIONARY, body, + NULL); + + /* Request MySpace info about ourself. */ + msim_send(session, + "persist", MSIM_TYPE_STRING, g_strdup("persist"), + "sesskey", MSIM_TYPE_INTEGER, session->sesskey, + "dsn", MSIM_TYPE_INTEGER, MG_OWN_IM_INFO_DSN, + "uid", MSIM_TYPE_INTEGER, session->userid, + "lid", MSIM_TYPE_INTEGER, MG_OWN_IM_INFO_LID, + "rid", MSIM_TYPE_INTEGER, session->next_rid++, + "body", MSIM_TYPE_STRING, g_strdup(""), + NULL); + + /* TODO: set options (persist cmd=514,dsn=1,lid=10) */ + /* TODO: set blocklist */ + + /* Notify servers of our current status. */ + purple_debug_info("msim", "msim_we_are_logged_on: notifying servers of status\n"); + msim_set_status(session->account, + purple_account_get_active_status(session->account)); + + /* TODO: setinfo */ + /* + body = msim_msg_new( + "TotalFriends", MSIM_TYPE_INTEGER, 666, + NULL); + msim_send(session, + "setinfo", MSIM_TYPE_BOOLEAN, TRUE, + "sesskey", MSIM_TYPE_INTEGER, session->sesskey, + "info", MSIM_TYPE_DICTIONARY, body, + NULL); + */ + + /* Disable due to problems with timeouts. TODO: fix. */ +#ifdef MSIM_USE_KEEPALIVE + purple_timeout_add(MSIM_KEEPALIVE_INTERVAL_CHECK, + (GSourceFunc)msim_check_alive, session); +#endif + + /* Check mail if they want to. */ + if (purple_account_get_check_mail(session->account)) { + session->inbox_handle = purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK, + (GSourceFunc)msim_check_inbox, session); + msim_check_inbox(session); + } + + msim_get_contact_list(session, MSIM_CONTACT_LIST_INITIAL_FRIENDS); + + return TRUE; +} + +/** + * Record the client version in the buddy list, from an incoming message. + */ +static gboolean +msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg) +{ + gchar *username, *cv; + gboolean ret; + MsimUser *user; + + username = msim_msg_get_string(msg, "_username"); + cv = msim_msg_get_string(msg, "cv"); + + g_return_val_if_fail(username != NULL, FALSE); + if (!cv) { + /* No client version to record, don't worry about it. */ + g_free(username); + return FALSE; + } + + user = msim_find_user(session, username); + + if (user) { + user->client_cv = atol(cv); + ret = TRUE; + } else { + ret = FALSE; + } + + g_free(username); + g_free(cv); + + return ret; +} + +#ifdef MSIM_SEND_CLIENT_VERSION +/** + * Send our client version to another unofficial client that understands it. + */ +static gboolean +msim_send_unofficial_client(MsimSession *session, gchar *username) +{ + gchar *our_info; + gboolean ret; + + our_info = g_strdup_printf("Libpurple %d.%d.%d - msimprpl %s", + PURPLE_MAJOR_VERSION, + PURPLE_MINOR_VERSION, + PURPLE_MICRO_VERSION, + MSIM_PRPL_VERSION_STRING); + + ret = msim_send_bm(session, username, our_info, MSIM_BM_UNOFFICIAL_CLIENT); + + return ret; +} +#endif + +/** + * Process incoming status messages. + * + * @param session + * @param msg Status update message. Caller frees. + * + * @return TRUE if successful. + */ +static gboolean +msim_incoming_status(MsimSession *session, MsimMessage *msg) +{ + PurpleBuddyList *blist; + MsimUser *user; + GList *list; + gchar *status_headline, *status_headline_escaped; + gint status_code, purple_status_code; + gchar *username; + gchar *unrecognized_msg; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + msim_msg_dump("msim_status msg=%s\n", msg); + + /* Helpfully looked up by msim_incoming_resolve() for us. */ + username = msim_msg_get_string(msg, "_username"); + g_return_val_if_fail(username != NULL, FALSE); + + { + gchar *ss; + + ss = msim_msg_get_string(msg, "msg"); + purple_debug_info("msim", + "msim_status: updating status for <%s> to <%s>\n", + username, ss ? ss : "(NULL)"); + g_free(ss); + } + + /* Example fields: + * |s|0|ss|Offline + * |s|1|ss|:-)|ls||ip|0|p|0 + */ + list = msim_msg_get_list(msg, "msg"); + + status_code = msim_msg_get_integer_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE)); + purple_debug_info("msim", "msim_status: %s's status code = %d\n", username, status_code); + status_headline = msim_msg_get_string_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE)); + + blist = purple_get_blist(); + + /* Add buddy if not found. + * TODO: Could this be responsible for #3444? */ + user = msim_find_user(session, username); + if (!user) { + PurpleBuddy *buddy; + + purple_debug_info("msim", + "msim_status: making new buddy for %s\n", username); + buddy = purple_buddy_new(session->account, username, NULL); + purple_blist_add_buddy(buddy, NULL, NULL, NULL); + + user = msim_get_user_from_buddy(buddy); + user->id = msim_msg_get_integer(msg, "f"); + + /* Keep track of the user ID across sessions */ + purple_blist_node_set_int(&buddy->node, "UserID", user->id); + + msim_store_user_info(session, msg, NULL); + } else { + purple_debug_info("msim", "msim_status: found buddy %s\n", username); + } + + if (status_headline && strcmp(status_headline, "") != 0) { + /* The status headline is plaintext, but libpurple treats it as HTML, + * so escape any HTML characters to their entity equivalents. */ + status_headline_escaped = g_markup_escape_text(status_headline, strlen(status_headline)); + } else { + status_headline_escaped = NULL; + } + + g_free(status_headline); + + if (user->headline) + g_free(user->headline); + + /* don't copy; let the MsimUser own the headline, memory-wise */ + user->headline = status_headline_escaped; + + /* Set user status */ + switch (status_code) { + case MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN: + purple_status_code = PURPLE_STATUS_OFFLINE; + break; + + case MSIM_STATUS_CODE_ONLINE: + purple_status_code = PURPLE_STATUS_AVAILABLE; + break; + + case MSIM_STATUS_CODE_AWAY: + purple_status_code = PURPLE_STATUS_AWAY; + break; + + case MSIM_STATUS_CODE_IDLE: + /* Treat idle as an available status. */ + purple_status_code = PURPLE_STATUS_AVAILABLE; + break; + + default: + purple_debug_info("msim", "msim_incoming_status for %s, unknown status code %d, treating as available\n", + username, status_code); + purple_status_code = PURPLE_STATUS_AVAILABLE; + + unrecognized_msg = g_strdup_printf("msim_incoming_status, unrecognized status code: %d\n", + status_code); + msim_unrecognized(session, NULL, unrecognized_msg); + g_free(unrecognized_msg); + + } + + purple_prpl_got_user_status(session->account, username, purple_primitive_get_id_from_type(purple_status_code), NULL); + + if (status_code == MSIM_STATUS_CODE_IDLE) { + purple_debug_info("msim", "msim_status: got idle: %s\n", username); + purple_prpl_got_user_idle(session->account, username, TRUE, time(NULL)); + } else { + /* All other statuses indicate going back to non-idle. */ + purple_prpl_got_user_idle(session->account, username, FALSE, time(NULL)); + } + +#ifdef MSIM_SEND_CLIENT_VERSION + if (status_code == MSIM_STATUS_CODE_ONLINE) { + /* Secretly whisper to unofficial clients our own version as they come online */ + msim_send_unofficial_client(session, username); + } +#endif + + if (status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN) { + /* Get information when they come online. + * TODO: periodically refresh? + */ + purple_debug_info("msim_incoming_status", "%s came online, looking up\n", username); + msim_lookup_user(session, username, NULL, NULL); + } + + g_free(username); + msim_msg_list_free(list); + + return TRUE; +} + +/** + * Handle an incoming instant message. + * + * @param session The session + * @param msg Message from the server, containing 'f' (userid from) and 'msg'. + * Should also contain username in _username from preprocessing. + * + * @return TRUE if successful. + */ +static gboolean +msim_incoming_im(MsimSession *session, MsimMessage *msg) +{ + gchar *username, *msg_msim_markup, *msg_purple_markup; + gchar *userid; + time_t time_received; + PurpleConversation *conv; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + username = msim_msg_get_string(msg, "_username"); + /* I know this isn't really a string... but we need it to be one for + * purple_find_conversation_with_account(). */ + userid = msim_msg_get_string(msg, "f"); + g_return_val_if_fail(username != NULL, FALSE); + + purple_debug_info("msim_incoming_im", "UserID is %s", userid); + + if (msim_is_userid(username)) { + purple_debug_info("msim", "Ignoring message from spambot (%s) on account %s\n", + username, purple_account_get_username(session->account)); + g_free(username); + return FALSE; + } + + /* See if a conversation with their UID already exists...*/ + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, userid, session->account); + if (conv) { + /* Since the conversation exists... We need to normalize it */ + purple_conversation_set_name(conv, username); + } + + msg_msim_markup = msim_msg_get_string(msg, "msg"); + g_return_val_if_fail(msg_msim_markup != NULL, FALSE); + + msg_purple_markup = msim_markup_to_html(session, msg_msim_markup); + g_free(msg_msim_markup); + + time_received = msim_msg_get_integer(msg, "date"); + if (!time_received) { + purple_debug_info("msim_incoming_im", "date in message not set.\n"); + time_received = time(NULL); + } + + serv_got_im(session->gc, username, msg_purple_markup, PURPLE_MESSAGE_RECV, time_received); + + g_free(username); + g_free(msg_purple_markup); + + return TRUE; +} + +/** + * Handle an incoming action message. + * + * @param session + * @param msg + * + * @return TRUE if successful. + */ +static gboolean +msim_incoming_action(MsimSession *session, MsimMessage *msg) +{ + gchar *msg_text, *username; + gboolean rc; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + msg_text = msim_msg_get_string(msg, "msg"); + g_return_val_if_fail(msg_text != NULL, FALSE); + + username = msim_msg_get_string(msg, "_username"); + g_return_val_if_fail(username != NULL, FALSE); + + purple_debug_info("msim", "msim_incoming_action: action <%s> from <%s>\n", + msg_text, username); + + if (g_str_equal(msg_text, "%typing%")) { + serv_got_typing(session->gc, username, 0, PURPLE_TYPING); + rc = TRUE; + } else if (g_str_equal(msg_text, "%stoptyping%")) { + serv_got_typing_stopped(session->gc, username); + rc = TRUE; + } else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_")) { + rc = msim_incoming_zap(session, msg); + } else if (strstr(msg_text, "!!!GroupCount=")) { + /* TODO: support group chats. I think the number in msg_text has + * something to do with the 'gid' field. */ + purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text); + + rc = TRUE; + } else if (strstr(msg_text, "!!!Offline=")) { + /* TODO: support group chats. This one might mean a user + * went offline or exited the chat. */ + purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text); + + rc = TRUE; + } else if (msim_msg_get_integer(msg, "aid") != 0) { + purple_debug_info("msim", "TODO: implement #4691, group chat from %d on %d: %s\n", + msim_msg_get_integer(msg, "aid"), + msim_msg_get_integer(msg, "f"), + msg_text); + + rc = TRUE; + } else { + msim_unrecognized(session, msg, + "got to msim_incoming_action but unrecognized value for 'msg'"); + rc = FALSE; + } + + g_free(msg_text); + g_free(username); + + return rc; +} + +/** + * Process an incoming media (message background?) message. + */ +static gboolean +msim_incoming_media(MsimSession *session, MsimMessage *msg) +{ + gchar *username, *text; + + username = msim_msg_get_string(msg, "_username"); + text = msim_msg_get_string(msg, "msg"); + + g_return_val_if_fail(username != NULL, FALSE); + g_return_val_if_fail(text != NULL, FALSE); + + purple_debug_info("msim", "msim_incoming_media: from %s, got msg=%s\n", username, text); + + /* Media messages are sent when the user opens a window to someone. + * Tell libpurple they started typing and stopped typing, to inform the Psychic + * Mode plugin so it too can open a window to the user. */ + serv_got_typing(session->gc, username, 0, PURPLE_TYPING); + serv_got_typing_stopped(session->gc, username); + + g_free(username); + + return TRUE; +} + +/** + * Process an incoming "unofficial client" message. The plugin for + * Miranda IM sends this message with the plugin information. + */ +static gboolean +msim_incoming_unofficial_client(MsimSession *session, MsimMessage *msg) +{ + MsimUser *user; + gchar *username, *client_info; + + username = msim_msg_get_string(msg, "_username"); + client_info = msim_msg_get_string(msg, "msg"); + + g_return_val_if_fail(username != NULL, FALSE); + g_return_val_if_fail(client_info != NULL, FALSE); + + purple_debug_info("msim", "msim_incoming_unofficial_client: %s is using client %s\n", + username, client_info); + + user = msim_find_user(session, username); + + g_return_val_if_fail(user != NULL, FALSE); + + if (user->client_info) { + g_free(user->client_info); + } + user->client_info = client_info; + + g_free(username); + /* Do not free client_info - the MsimUser now owns it. */ + + return TRUE; +} + +/** + * Handle an incoming buddy message. + */ +static gboolean +msim_incoming_bm(MsimSession *session, MsimMessage *msg) +{ + guint bm; + + bm = msim_msg_get_integer(msg, "bm"); + + msim_incoming_bm_record_cv(session, msg); + + switch (bm) { + case MSIM_BM_STATUS: + return msim_incoming_status(session, msg); + case MSIM_BM_INSTANT: + return msim_incoming_im(session, msg); + case MSIM_BM_ACTION: + return msim_incoming_action(session, msg); + case MSIM_BM_MEDIA: + return msim_incoming_media(session, msg); + case MSIM_BM_UNOFFICIAL_CLIENT: + return msim_incoming_unofficial_client(session, msg); + default: + /* Not really an IM, but show it for informational + * purposes during development. */ + return msim_incoming_im(session, msg); + } +} + +/** + * Process the initial server information from the server. + */ +static gboolean +msim_process_server_info(MsimSession *session, MsimMessage *msg) +{ + MsimMessage *body; + + body = msim_msg_get_dictionary(msg, "body"); + g_return_val_if_fail(body != NULL, FALSE); + + /* Example body: +AdUnitRefreshInterval=10. +AlertPollInterval=360. +AllowChatRoomEmoticonSharing=False. +ChatRoomUserIDs=78744676;163733130;1300326231;123521495;142663391. +CurClientVersion=673. +EnableIMBrowse=True. +EnableIMStuffAvatars=False. +EnableIMStuffZaps=False. +MaxAddAllFriends=100. +MaxContacts=1000. +MinClientVersion=594. +MySpaceIM_ENGLISH=78744676. +MySpaceNowTimer=720. +PersistenceDataTimeout=900. +UseWebChallenge=1. +WebTicketGoHome=False + + Anything useful? TODO: use what is useful, and use it. +*/ + purple_debug_info("msim_process_server_info", + "maximum contacts: %d\n", + msim_msg_get_integer(body, "MaxContacts")); + + session->server_info = body; + /* session->server_info freed in msim_session_destroy */ + + return TRUE; +} + +/** + * Process a web challenge, used to login to the web site. + */ +static gboolean +msim_web_challenge(MsimSession *session, MsimMessage *msg) +{ + /* TODO: web challenge, store token. #2659. */ + return FALSE; +} + +/** + * Process a persistance message reply from the server. + * + * @param session + * @param msg Message reply from server. + * + * @return TRUE if successful. + * + * msim_lookup_user sets callback for here + */ +static gboolean +msim_process_reply(MsimSession *session, MsimMessage *msg) +{ + MSIM_USER_LOOKUP_CB cb; + gpointer data; + guint rid, cmd, dsn, lid; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + msim_store_user_info(session, msg, NULL); + + rid = msim_msg_get_integer(msg, "rid"); + cmd = msim_msg_get_integer(msg, "cmd"); + dsn = msim_msg_get_integer(msg, "dsn"); + lid = msim_msg_get_integer(msg, "lid"); + + /* Unsolicited messages */ + if (cmd == (MSIM_CMD_BIT_REPLY | MSIM_CMD_GET)) { + if (dsn == MG_SERVER_INFO_DSN && lid == MG_SERVER_INFO_LID) { + return msim_process_server_info(session, msg); + } else if (dsn == MG_WEB_CHALLENGE_DSN && lid == MG_WEB_CHALLENGE_LID) { + return msim_web_challenge(session, msg); + } + } + + /* If a callback is registered for this userid lookup, call it. */ + cb = g_hash_table_lookup(session->user_lookup_cb, GUINT_TO_POINTER(rid)); + data = g_hash_table_lookup(session->user_lookup_cb_data, GUINT_TO_POINTER(rid)); + + if (cb) { + purple_debug_info("msim", "msim_process_reply: calling callback now\n"); + msim_msg_dump("for msg=%s\n", msg); + /* Clone message, so that the callback 'cb' can use it (needs to free it also). */ + cb(session, msim_msg_clone(msg), data); + g_hash_table_remove(session->user_lookup_cb, GUINT_TO_POINTER(rid)); + g_hash_table_remove(session->user_lookup_cb_data, GUINT_TO_POINTER(rid)); + } else { + purple_debug_info("msim", + "msim_process_reply: no callback for rid %d\n", rid); + } + + return TRUE; +} + +/** + * Handle an error from the server. + * + * @param session + * @param msg The message. + * + * @return TRUE if successfully reported error. + */ +static gboolean +msim_error(MsimSession *session, MsimMessage *msg) +{ + gchar *errmsg, *full_errmsg; + guint err; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + err = msim_msg_get_integer(msg, "err"); + errmsg = msim_msg_get_string(msg, "errmsg"); + + full_errmsg = g_strdup_printf(_("Protocol error, code %d: %s"), err, + errmsg ? errmsg : "no 'errmsg' given"); + + g_free(errmsg); + + purple_debug_info("msim", "msim_error (sesskey=%d): %s\n", + session->sesskey, full_errmsg); + + /* Destroy session if fatal. */ + if (msim_msg_get(msg, "fatal")) { + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + purple_debug_info("msim", "fatal error, closing\n"); + + switch (err) { + case MSIM_ERROR_INCORRECT_PASSWORD: /* Incorrect password */ + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; + if (!purple_account_get_remember_password(session->account)) + purple_account_set_password(session->account, NULL); +#ifdef MSIM_MAX_PASSWORD_LENGTH + if (session->account->password && (strlen(session->account->password) > MSIM_MAX_PASSWORD_LENGTH)) { + gchar *suggestion; + + suggestion = g_strdup_printf(_("%s Your password is " + "%d characters, greater than the " + "expected maximum length of %d for " + "MySpaceIM. Please shorten your " + "password at http://profileedit.myspace.com/index.cfm?fuseaction=accountSettings.changePassword and try again."), + full_errmsg, (int) + strlen(session->account->password), + MSIM_MAX_PASSWORD_LENGTH); + + /* Replace full_errmsg. */ + g_free(full_errmsg); + full_errmsg = suggestion; + } +#endif + break; + case MSIM_ERROR_LOGGED_IN_ELSEWHERE: /* Logged in elsewhere */ + reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE; + if (!purple_account_get_remember_password(session->account)) + purple_account_set_password(session->account, NULL); + break; + } + purple_connection_error_reason (session->gc, reason, full_errmsg); + } else { + purple_notify_error(session->account, _("MySpaceIM Error"), full_errmsg, NULL); + } + + g_free(full_errmsg); + + return TRUE; +} + +/** + * Process a message. + * + * @param session + * @param msg A message from the server, ready for processing (possibly with resolved username information attached). Caller frees. + * + * @return TRUE if successful. FALSE if processing failed. + */ +static gboolean +msim_process(MsimSession *session, MsimMessage *msg) +{ + g_return_val_if_fail(session != NULL, FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + +#ifdef MSIM_DEBUG_MSG + msim_msg_dump("ready to process: %s\n", msg); +#endif + + if (msim_msg_get_integer(msg, "lc") == 1) { + return msim_login_challenge(session, msg); + } else if (msim_msg_get_integer(msg, "lc") == 2) { + /* return msim_we_are_logged_on(session, msg); */ + if (msim_is_username_set(session, msg)) { + return msim_we_are_logged_on(session); + } else { + /* No username is set... We'll wait for the callbacks to do their work */ + /* When they're all done, the last one will call msim_we_are_logged_on() and pick up where we left off */ + return FALSE; + } + } else if (msim_msg_get(msg, "bm")) { + return msim_incoming_bm(session, msg); + } else if (msim_msg_get(msg, "rid")) { + return msim_process_reply(session, msg); + } else if (msim_msg_get(msg, "error")) { + return msim_error(session, msg); + } else if (msim_msg_get(msg, "ka")) { + return TRUE; + } else { + msim_unrecognized(session, msg, "in msim_process"); + return FALSE; + } +} + +/** + * After a uid is resolved to username, tag it with the username and submit for processing. + * + * @param session + * @param userinfo Response messsage to resolving request. + * @param data MsimMessage *, the message to attach information to. + */ +static void +msim_incoming_resolved(MsimSession *session, MsimMessage *userinfo, + gpointer data) +{ + gchar *username; + MsimMessage *msg, *body; + + g_return_if_fail(MSIM_SESSION_VALID(session)); + g_return_if_fail(userinfo != NULL); + + body = msim_msg_get_dictionary(userinfo, "body"); g_return_if_fail(body != NULL); + + username = msim_msg_get_string(body, "UserName"); + g_return_if_fail(username != NULL); + /* Note: username will be owned by 'msg' below. */ + + msg = (MsimMessage *)data; + g_return_if_fail(msg != NULL); + + /* TODO: more elegant solution than below. attach whole message? */ + /* Special elements name beginning with '_', we'll use internally within the + * program (did not come directly from the wire). */ + msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, username); /* This makes 'msg' the owner of 'username' */ + + /* TODO: attach more useful information, like ImageURL */ + + msim_process(session, msg); + + /* TODO: Free copy cloned from msim_preprocess_incoming(). */ + /* msim_msg_free(msg); */ msim_msg_free(body); - if (!g_str_equal(completed, "True")) - { - purple_debug_info("msim_import_friends_cb", - "failed to import friends: %s", completed); - purple_notify_error(session->account, _("Add friends from MySpace.com"), - _("Importing friends failed"), NULL); - g_free(completed); +} + +/** + * Preprocess incoming messages, resolving as needed, calling + * msim_process() when ready to process. + * + * @param session + * @param msg MsimMessage *, freed by caller. + */ +static gboolean +msim_preprocess_incoming(MsimSession *session, MsimMessage *msg) +{ + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + if (msim_msg_get(msg, "bm") && msim_msg_get(msg, "f")) { + guint uid; + const gchar *username; + + /* 'f' = userid message is from, in buddy messages */ + uid = msim_msg_get_integer(msg, "f"); + + username = msim_uid2username_from_blist(session->account, uid); + + if (username) { + /* Know username already, use it. */ + purple_debug_info("msim", "msim_preprocess_incoming: tagging with _username=%s\n", + username); + msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username)); + return msim_process(session, msg); + + } else { + gchar *from; + + /* Send lookup request. */ + /* XXX: where is msim_msg_get_string() freed? make _strdup and _nonstrdup. */ + purple_debug_info("msim", "msim_incoming: sending lookup, setting up callback\n"); + from = msim_msg_get_string(msg, "f"); + msim_lookup_user(session, from, msim_incoming_resolved, msim_msg_clone(msg)); + g_free(from); + + /* indeterminate */ + return TRUE; + } + } else { + /* Nothing to resolve - send directly to processing. */ + return msim_process(session, msg); + } +} + +/** + * Callback when input available. + * + * @param gc_uncasted A PurpleConnection pointer. + * @param source File descriptor. + * @param cond PURPLE_INPUT_READ + * + * Reads the input, and calls msim_preprocess_incoming() to handle it. + */ +static void +msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond) +{ + PurpleConnection *gc; + PurpleAccount *account; + MsimSession *session; + gchar *end; + int n; + + g_return_if_fail(gc_uncasted != NULL); + g_return_if_fail(source >= 0); /* Note: 0 is a valid fd */ + + gc = (PurpleConnection *)(gc_uncasted); + account = purple_connection_get_account(gc); + session = gc->proto_data; + + /* libpurple/eventloop.h only defines these two */ + if (cond != PURPLE_INPUT_READ && cond != PURPLE_INPUT_WRITE) { + purple_debug_info("msim_input_cb", "unknown condition=%d\n", cond); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid input condition")); return; } - g_free(completed); - - purple_debug_info("msim_import_friends_cb", - "added friends to server-side buddy list, requesting new contacts from server"); - - msim_get_contact_list(session, MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS); - - /* TODO: show, X friends have been added */ + + g_return_if_fail(cond == PURPLE_INPUT_READ); + g_return_if_fail(MSIM_SESSION_VALID(session)); + + /* Mark down that we got data, so we don't timeout. */ + session->last_comm = time(NULL); + + /* If approaching end of buffer, reallocate some more memory. */ + if (session->rxsize < session->rxoff + MSIM_READ_BUF_SIZE) { + purple_debug_info("msim", + "msim_input_cb: %d-byte read buffer full, rxoff=%d, " "growing by %d bytes\n", + session->rxsize, session->rxoff, MSIM_READ_BUF_SIZE); + session->rxsize += MSIM_READ_BUF_SIZE; + session->rxbuf = g_realloc(session->rxbuf, session->rxsize); + + return; + } + + purple_debug_info("msim", "dynamic buffer at %d (max %d), reading up to %d\n", + session->rxoff, session->rxsize, + MSIM_READ_BUF_SIZE - session->rxoff - 1); + + /* Read into buffer. On Win32, need recv() not read(). session->fd also holds + * the file descriptor, but it sometimes differs from the 'source' parameter. + */ + n = recv(session->fd, + session->rxbuf + session->rxoff, + session->rxsize - session->rxoff - 1, 0); + + if (n < 0 && errno == EAGAIN) { + return; + } else if (n < 0) { + purple_debug_error("msim", "msim_input_cb: read error, ret=%d, " + "error=%s, source=%d, fd=%d (%X))\n", + n, g_strerror(errno), source, session->fd, session->fd); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Read error")); + return; + } else if (n == 0) { + purple_debug_info("msim", "msim_input_cb: server disconnected\n"); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server has disconnected")); + return; + } + + if (n + session->rxoff > session->rxsize) { + purple_debug_info("msim_input_cb", "received %d bytes, pushing rxoff to %d, over buffer size of %d\n", + n, n + session->rxoff, session->rxsize); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Read buffer full (2)")); + return; + } + + /* Null terminate */ + purple_debug_info("msim", "msim_input_cb: going to null terminate " + "at n=%d\n", n); + session->rxbuf[session->rxoff + n] = 0; + +#ifdef MSIM_CHECK_EMBEDDED_NULLS + /* Check for embedded NULs. I don't handle them, and they shouldn't occur. */ + if (strlen(session->rxbuf + session->rxoff) != n) { + /* Occurs after login, but it is not a null byte. */ + purple_debug_info("msim", "msim_input_cb: strlen=%d, but read %d bytes" + "--null byte encountered?\n", + strlen(session->rxbuf + session->rxoff), n); + /*purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + "Invalid message - null byte on input"); */ + return; + } +#endif + + session->rxoff += n; + purple_debug_info("msim", "msim_input_cb: read=%d\n", n); + +#ifdef MSIM_DEBUG_RXBUF + purple_debug_info("msim", "buf=<%s>\n", session->rxbuf); +#endif + + /* Look for \\final\\ end markers. If found, process message. */ + while((end = strstr(session->rxbuf, MSIM_FINAL_STRING))) { + MsimMessage *msg; + +#ifdef MSIM_DEBUG_RXBUF + purple_debug_info("msim", "in loop: buf=<%s>\n", session->rxbuf); +#endif + *end = 0; + msg = msim_parse(session->rxbuf); + if (!msg) { + purple_debug_info("msim", "msim_input_cb: couldn't parse rxbuf\n"); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unparseable message")); + break; + } else { + /* Process message and then free it (processing function should + * clone message if it wants to keep it afterwards.) */ + if (!msim_preprocess_incoming(session, msg)) { + msim_msg_dump("msim_input_cb: preprocessing message failed on msg: %s\n", msg); + } + msim_msg_free(msg); + } + + /* Move remaining part of buffer to beginning. */ + session->rxoff -= strlen(session->rxbuf) + strlen(MSIM_FINAL_STRING); + memmove(session->rxbuf, end + strlen(MSIM_FINAL_STRING), + session->rxsize - (end + strlen(MSIM_FINAL_STRING) - session->rxbuf)); + + /* Clear end of buffer + * memset(end, 0, MSIM_READ_BUF_SIZE - (end - session->rxbuf)); + */ + } } -/** Import friends from myspace.com. */ -static void msim_import_friends(PurplePluginAction *action) +/** + * Callback when connected. Sets up input handlers. + * + * @param data A PurpleConnection pointer. + * @param source File descriptor. + * @param error_message + */ +static void +msim_connect_cb(gpointer data, gint source, const gchar *error_message) { PurpleConnection *gc; MsimSession *session; - gchar *group_name; - - gc = (PurpleConnection *)action->context; + + g_return_if_fail(data != NULL); + + gc = (PurpleConnection *)data; + session = (MsimSession *)gc->proto_data; + + if (source < 0) { + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + g_strdup_printf(_("Couldn't connect to host: %s (%d)"), + error_message ? error_message : "no message given", + source)); + return; + } + + session->fd = source; + + gc->inpa = purple_input_add(source, PURPLE_INPUT_READ, msim_input_cb, gc); +} + +/** + * Start logging in to the MSIM servers. + * + * @param acct Account information to use to login. + */ +static void +msim_login(PurpleAccount *acct) +{ + PurpleConnection *gc; + const gchar *host; + int port; + + g_return_if_fail(acct != NULL); + g_return_if_fail(acct->username != NULL); + + purple_debug_info("msim", "logging in %s\n", acct->username); + + gc = purple_account_get_connection(acct); + gc->proto_data = msim_session_new(acct); + gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_NO_URLDESC; + + /* 1. connect to server */ + purple_connection_update_progress(gc, _("Connecting"), + 0, /* which connection step this is */ + 4); /* total number of steps */ + + host = purple_account_get_string(acct, "server", MSIM_SERVER); + port = purple_account_get_int(acct, "port", MSIM_PORT); + + /* From purple.sf.net/api: + * """Note that this function name can be misleading--although it is called + * "proxy connect," it is used for establishing any outgoing TCP connection, + * whether through a proxy or not.""" */ + + /* Calls msim_connect_cb when connected. */ + if (!purple_proxy_connect(gc, acct, host, port, msim_connect_cb, gc)) { + /* TODO: try other ports if in auto mode, then save + * working port and try that first next time. */ + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Couldn't create socket")); + return; + } +} + +/** + * Close the connection. + * + * @param gc The connection. + */ +static void +msim_close(PurpleConnection *gc) +{ + MsimSession *session; + + if (gc == NULL) { + return; + } + + session = (MsimSession *)gc->proto_data; + if (session == NULL) + return; + + gc->proto_data = NULL; + + if (!MSIM_SESSION_VALID(session)) { + return; + } + + if (session->gc->inpa) { + purple_input_remove(session->gc->inpa); + } + + msim_session_destroy(session); +} + +/** + * Schedule an IM to be sent once the user ID is looked up. + * + * @param gc Connection. + * @param who A user id, email, or username to send the message to. + * @param message Instant message text to send. + * @param flags Flags. + * + * @return 1 if successful or postponed, -1 if failed + * + * Allows sending to a user by username, email address, or userid. If + * a username or email address is given, the userid must be looked up. + * This function does that by calling msim_postprocess_outgoing(). + */ +static int +msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, + PurpleMessageFlags flags) +{ + MsimSession *session; + gchar *message_msim; + int rc; + + g_return_val_if_fail(gc != NULL, -1); + g_return_val_if_fail(who != NULL, -1); + g_return_val_if_fail(message != NULL, -1); + + /* 'flags' has many options, not used here. */ + + session = (MsimSession *)gc->proto_data; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); + + message_msim = html_to_msim_markup(session, message); + + if (msim_send_bm(session, who, message_msim, MSIM_BM_INSTANT)) { + /* Return 1 to have Purple show this IM as being sent, 0 to not. I always + * return 1 even if the message could not be sent, since I don't know if + * it has failed yet--because the IM is only sent after the userid is + * retrieved from the server (which happens after this function returns). + * If an error does occur, it should be logged to the IM window. + */ + rc = 1; + } else { + rc = -1; + } + + g_free(message_msim); + + return rc; +} + +/** + * Handle when our user starts or stops typing to another user. + * + * @param gc + * @param name The buddy name to which our user is typing to + * @param state PURPLE_TYPING, PURPLE_TYPED, PURPLE_NOT_TYPING + * + * @return 0 + */ +static unsigned int +msim_send_typing(PurpleConnection *gc, const gchar *name, + PurpleTypingState state) +{ + const gchar *typing_str; + MsimSession *session; + + g_return_val_if_fail(gc != NULL, 0); + g_return_val_if_fail(name != NULL, 0); + session = (MsimSession *)gc->proto_data; - group_name = "MySpace Friends"; - - g_return_if_fail(msim_send(session, + g_return_val_if_fail(MSIM_SESSION_VALID(session), 0); + + switch (state) { + case PURPLE_TYPING: + typing_str = "%typing%"; + break; + + case PURPLE_TYPED: + case PURPLE_NOT_TYPING: + default: + typing_str = "%stoptyping%"; + break; + } + + purple_debug_info("msim", "msim_send_typing(%s): %d (%s)\n", name, state, typing_str); + msim_send_bm(session, name, typing_str, MSIM_BM_ACTION); + return 0; +} + +/** + * Callback for msim_get_info(), for when user info is received. + */ +static void +msim_get_info_cb(MsimSession *session, MsimMessage *user_info_msg, + gpointer data) +{ + MsimMessage *msg; + gchar *username; + PurpleNotifyUserInfo *user_info; + MsimUser *user; + + g_return_if_fail(MSIM_SESSION_VALID(session)); + + /* Get user{name,id} from msim_get_info, passed as an MsimMessage for + orthogonality. */ + msg = (MsimMessage *)data; + g_return_if_fail(msg != NULL); + + username = msim_msg_get_string(msg, "user"); + if (!username) { + purple_debug_info("msim", "msim_get_info_cb: no 'user' in msg\n"); + return; + } + + msim_msg_free(msg); + purple_debug_info("msim", "msim_get_info_cb: got for user: %s\n", username); + + user = msim_find_user(session, username); + + if (!user) { + /* User isn't on blist, create a temporary user to store info. */ + user = g_new0(MsimUser, 1); + user->temporary_user = TRUE; + } + + /* Update user structure with new information */ + msim_store_user_info(session, user_info_msg, user); + + user_info = purple_notify_user_info_new(); + + /* Append data from MsimUser to PurpleNotifyUserInfo for display, full */ + msim_append_user_info(session, user_info, user, TRUE); + + purple_notify_userinfo(session->gc, username, user_info, NULL, NULL); + purple_debug_info("msim", "msim_get_info_cb: username=%s\n", username); + + purple_notify_user_info_destroy(user_info); + + if (user->temporary_user) { + g_free(user->client_info); + g_free(user->gender); + g_free(user->location); + g_free(user->headline); + g_free(user->display_name); + g_free(user->username); + g_free(user->image_url); + g_free(user); + } + g_free(username); +} + +/** + * Retrieve a user's profile. + * @param username Username, user ID, or email address to lookup. + */ +static void +msim_get_info(PurpleConnection *gc, const gchar *username) +{ + MsimSession *session; + MsimUser *user; + gchar *user_to_lookup; + MsimMessage *user_msg; + + g_return_if_fail(gc != NULL); + g_return_if_fail(username != NULL); + + session = (MsimSession *)gc->proto_data; + + g_return_if_fail(MSIM_SESSION_VALID(session)); + + /* Obtain uid of buddy. */ + user = msim_find_user(session, username); + + /* If is on buddy list, lookup by uid since it is faster. */ + if (user && user->id) { + user_to_lookup = g_strdup_printf("%d", user->id); + } else { + /* Looking up buddy not on blist. Lookup by whatever user entered. */ + user_to_lookup = g_strdup(username); + } + + /* Pass the username to msim_get_info_cb(), because since we lookup + * by userid, the userinfo message will only contain the uid (not + * the username) but it would be useful to display the username too. + */ + user_msg = msim_msg_new( + "user", MSIM_TYPE_STRING, g_strdup(username), + NULL); + purple_debug_info("msim", "msim_get_info, setting up lookup, user=%s\n", username); + + msim_lookup_user(session, user_to_lookup, msim_get_info_cb, user_msg); + + g_free(user_to_lookup); +} + +/** + * Set status using an MSIM_STATUS_CODE_* value. + * @param status_code An MSIM_STATUS_CODE_* value. + * @param statstring Status string, must be a dynamic string (will be freed by msim_send). + */ +static void +msim_set_status_code(MsimSession *session, guint status_code, gchar *statstring) +{ + g_return_if_fail(MSIM_SESSION_VALID(session)); + g_return_if_fail(statstring != NULL); + + purple_debug_info("msim", "msim_set_status_code: going to set status to code=%d,str=%s\n", + status_code, statstring); + + if (!msim_send(session, + "status", MSIM_TYPE_INTEGER, status_code, + "sesskey", MSIM_TYPE_INTEGER, session->sesskey, + "statstring", MSIM_TYPE_STRING, statstring, + "locstring", MSIM_TYPE_STRING, g_strdup(""), + NULL)) + { + purple_debug_info("msim", "msim_set_status: failed to set status\n"); + } +} + +/** + * Set your status - callback for when user manually sets it. + */ +static void +msim_set_status(PurpleAccount *account, PurpleStatus *status) +{ + PurpleStatusType *type; + PurplePresence *pres; + MsimSession *session; + guint status_code; + const gchar *message; + gchar *stripped; + gchar *unrecognized_msg; + + session = (MsimSession *)account->gc->proto_data; + + g_return_if_fail(MSIM_SESSION_VALID(session)); + + type = purple_status_get_type(status); + pres = purple_status_get_presence(status); + + switch (purple_status_type_get_primitive(type)) { + case PURPLE_STATUS_AVAILABLE: + purple_debug_info("msim", "msim_set_status: available (%d->%d)\n", PURPLE_STATUS_AVAILABLE, + MSIM_STATUS_CODE_ONLINE); + status_code = MSIM_STATUS_CODE_ONLINE; + break; + + case PURPLE_STATUS_INVISIBLE: + purple_debug_info("msim", "msim_set_status: invisible (%d->%d)\n", PURPLE_STATUS_INVISIBLE, + MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN); + status_code = MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN; + break; + + case PURPLE_STATUS_AWAY: + purple_debug_info("msim", "msim_set_status: away (%d->%d)\n", PURPLE_STATUS_AWAY, + MSIM_STATUS_CODE_AWAY); + status_code = MSIM_STATUS_CODE_AWAY; + break; + + default: + purple_debug_info("msim", "msim_set_status: unknown " + "status interpreting as online"); + status_code = MSIM_STATUS_CODE_ONLINE; + + unrecognized_msg = g_strdup_printf("msim_set_status, unrecognized status type: %d\n", + purple_status_type_get_primitive(type)); + msim_unrecognized(session, NULL, unrecognized_msg); + g_free(unrecognized_msg); + + break; + } + + message = purple_status_get_attr_string(status, "message"); + + /* Status strings are plain text. */ + if (message != NULL) + stripped = purple_markup_strip_html(message); + else + stripped = g_strdup(""); + + msim_set_status_code(session, status_code, stripped); + + /* If we should be idle, set that status. Time is irrelevant here. */ + if (purple_presence_is_idle(pres) && status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN) + msim_set_idle(account->gc, 1); +} + +/** + * Go idle. + */ +static void +msim_set_idle(PurpleConnection *gc, int time) +{ + MsimSession *session; + PurpleStatus *status; + + g_return_if_fail(gc != NULL); + + session = (MsimSession *)gc->proto_data; + + g_return_if_fail(MSIM_SESSION_VALID(session)); + + status = purple_account_get_active_status(session->account); + + if (time == 0) { + /* Going back from idle. In msim, idle is mutually exclusive + * from the other states (you can only be away or idle, but not + * both, for example), so by going non-idle I go back to what + * libpurple says I should be. + */ + msim_set_status(session->account, status); + } else { + const gchar *message; + gchar *stripped; + + /* Set the idle message to the status message from the real + * current status. + */ + message = purple_status_get_attr_string(status, "message"); + if (message != NULL) + stripped = purple_markup_strip_html(message); + else + stripped = g_strdup(""); + + /* msim doesn't support idle time, so just go idle */ + msim_set_status_code(session, MSIM_STATUS_CODE_IDLE, stripped); + } +} + +/** + * Add a buddy to user's buddy list. + */ +static void +msim_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) +{ + MsimSession *session; + MsimMessage *msg; + MsimMessage *msg_persist; + MsimMessage *body; + + session = (MsimSession *)gc->proto_data; + purple_debug_info("msim", "msim_add_buddy: want to add %s to %s\n", + buddy->name, (group && group->name) ? group->name : "(no group)"); + + msg = msim_msg_new( + "addbuddy", MSIM_TYPE_BOOLEAN, TRUE, + "sesskey", MSIM_TYPE_INTEGER, session->sesskey, + /* "newprofileid" will be inserted here with uid. */ + "reason", MSIM_TYPE_STRING, g_strdup(""), + NULL); + + if (!msim_postprocess_outgoing(session, msg, buddy->name, "newprofileid", "reason")) { + purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("'addbuddy' command failed.")); + msim_msg_free(msg); + return; + } + msim_msg_free(msg); + + /* TODO: if addbuddy fails ('error' message is returned), delete added buddy from + * buddy list since Purple adds it locally. */ + + body = msim_msg_new( + "ContactID", MSIM_TYPE_STRING, g_strdup("<uid>"), + "GroupName", MSIM_TYPE_STRING, g_strdup(group->name), + "Position", MSIM_TYPE_INTEGER, 1000, + "Visibility", MSIM_TYPE_INTEGER, 1, + "NickName", MSIM_TYPE_STRING, g_strdup(""), + "NameSelect", MSIM_TYPE_INTEGER, 0, + NULL); + + /* TODO: Update blocklist. */ + + msg_persist = msim_msg_new( + "persist", MSIM_TYPE_INTEGER, 1, + "sesskey", MSIM_TYPE_INTEGER, session->sesskey, + "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_PUT, + "dsn", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_DSN, + "uid", MSIM_TYPE_INTEGER, session->userid, + "lid", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_LID, + /* TODO: Use msim_new_reply_callback to get rid. */ + "rid", MSIM_TYPE_INTEGER, session->next_rid++, + "body", MSIM_TYPE_DICTIONARY, body, + NULL); + + if (!msim_postprocess_outgoing(session, msg_persist, buddy->name, "body", NULL)) + { + purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("persist command failed")); + msim_msg_free(msg_persist); + return; + } + msim_msg_free(msg_persist); +} + +/** + * Remove a buddy from the user's buddy list. + */ +static void +msim_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) +{ + MsimSession *session; + MsimMessage *delbuddy_msg; + MsimMessage *persist_msg; + MsimMessage *blocklist_msg; + GList *blocklist_updates; + + session = (MsimSession *)gc->proto_data; + + delbuddy_msg = msim_msg_new( + "delbuddy", MSIM_TYPE_BOOLEAN, TRUE, + "sesskey", MSIM_TYPE_INTEGER, session->sesskey, + /* 'delprofileid' with uid will be inserted here. */ + NULL); + + if (!msim_postprocess_outgoing(session, delbuddy_msg, buddy->name, "delprofileid", NULL)) { + purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("'delbuddy' command failed")); + msim_msg_free(delbuddy_msg); + return; + } + msim_msg_free(delbuddy_msg); + + persist_msg = msim_msg_new( "persist", MSIM_TYPE_INTEGER, 1, "sesskey", MSIM_TYPE_INTEGER, session->sesskey, - "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_PUT, - "dsn", MSIM_TYPE_INTEGER, MC_IMPORT_ALL_FRIENDS_DSN, - "lid", MSIM_TYPE_INTEGER, MC_IMPORT_ALL_FRIENDS_LID, + "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_DELETE, + "dsn", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_DSN, + "lid", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_LID, "uid", MSIM_TYPE_INTEGER, session->userid, - "rid", MSIM_TYPE_INTEGER, - msim_new_reply_callback(session, msim_import_friends_cb, NULL), - "body", MSIM_TYPE_STRING, - g_strdup_printf("GroupName=%s", group_name), - NULL)); - - + "rid", MSIM_TYPE_INTEGER, session->next_rid++, + /* <uid> will be replaced by postprocessing */ + "body", MSIM_TYPE_STRING, g_strdup("ContactID=<uid>"), + NULL); + + if (!msim_postprocess_outgoing(session, persist_msg, buddy->name, "body", NULL)) { + purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("persist command failed")); + msim_msg_free(persist_msg); + return; + } + msim_msg_free(persist_msg); + + blocklist_updates = NULL; + blocklist_updates = g_list_prepend(blocklist_updates, "a-"); + blocklist_updates = g_list_prepend(blocklist_updates, "<uid>"); + blocklist_updates = g_list_prepend(blocklist_updates, "b-"); + blocklist_updates = g_list_prepend(blocklist_updates, "<uid>"); + blocklist_updates = g_list_reverse(blocklist_updates); + + blocklist_msg = msim_msg_new( + "blocklist", MSIM_TYPE_BOOLEAN, TRUE, + "sesskey", MSIM_TYPE_INTEGER, session->sesskey, + /* TODO: MsimMessage lists. Currently <uid> isn't replaced in lists. */ + /* "idlist", MSIM_TYPE_STRING, g_strdup("a-|<uid>|b-|<uid>"), */ + "idlist", MSIM_TYPE_LIST, blocklist_updates, + NULL); + + if (!msim_postprocess_outgoing(session, blocklist_msg, buddy->name, "idlist", NULL)) { + purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("blocklist command failed")); + msim_msg_free(blocklist_msg); + return; + } + msim_msg_free(blocklist_msg); } -/** Actions menu for account. */ -GList * -msim_actions(PurplePlugin *plugin, gpointer context) +/** + * Returns a string of a username in canonical form. Basically removes all the + * spaces, lowercases the string, and looks up user IDs to usernames. + * Normalizing tom, TOM, Tom, and 6221 wil all return 'tom'. + * + * Borrowed this code from oscar_normalize. Added checking for + * "if userid, get name before normalizing" + */ +static const char *msim_normalize(const PurpleAccount *account, const char *str) { + static char normalized[BUF_LEN]; + char *tmp1, *tmp2; + int i, j; + guint id; + + g_return_val_if_fail(str != NULL, NULL); + + if (msim_is_userid(str)) { + /* Have user ID, we need to get their username first :) */ + const char *username; + + /* If the account does not exist, we can't look up the user. */ + if (!account || !account->gc) + return str; + + id = atol(str); + username = msim_uid2username_from_blist((PurpleAccount *)account, id); + if (!username) { + /* Not in buddy list... scheisse... TODO: Manual Lookup! Bug #4631 */ + /* Note: manual lookup using msim_lookup_user() is a problem inside + * msim_normalize(), because msim_lookup_user() calls a callback function + * when the user information has been looked up, but msim_normalize() expects + * the result immediately. */ + strncpy(normalized, str, BUF_LEN); + } else { + strncpy(normalized, username, BUF_LEN); + } + } else { + /* Have username. */ + strncpy(normalized, str, BUF_LEN); + } + + /* Strip spaces. */ + for (i=0, j=0; normalized[j]; i++, j++) { + while (normalized[j] == ' ') + j++; + normalized[i] = normalized[j]; + } + normalized[i] = '\0'; + + /* Lowercase and perform UTF-8 normalization. */ + tmp1 = g_utf8_strdown(normalized, -1); + tmp2 = g_utf8_normalize(tmp1, -1, G_NORMALIZE_DEFAULT); + g_snprintf(normalized, sizeof(normalized), "%s", tmp2); + g_free(tmp2); + g_free(tmp1); + + /* TODO: re-add caps and spacing back to what the user wanted. + * User can format their own names, for example 'msimprpl' is shown + * as 'MsIm PrPl' in the official client. + * + * TODO: file a ticket to add this enhancement. + */ + + return normalized; +} + +/** + * Return whether the buddy can be messaged while offline. + * + * The protocol supports offline messages in just the same way as online + * messages. + */ +static gboolean +msim_offline_message(const PurpleBuddy *buddy) { - PurpleConnection *gc; - GList *menu; - PurplePluginAction *act; - - gc = (PurpleConnection *)context; - - menu = NULL; - -#if 0 - /* TODO: find out how */ - act = purple_plugin_action_new(_("Find people..."), msim_); - menu = g_list_append(menu, act); - - act = purple_plugin_action_new(_("Change IM name..."), NULL); - menu = g_list_append(menu, act); -#endif - - act = purple_plugin_action_new(_("Add friends from MySpace.com"), msim_import_friends); - menu = g_list_append(menu, act); - - return menu; + return TRUE; } -/** Callbacks called by Purple, to access this plugin. */ +/** + * Send raw data to the server, possibly with embedded NULs. + * + * Used in prpl_info struct, so that plugins can have the most possible + * control of what is sent over the connection. Inside this prpl, + * msim_send_raw() is used, since it sends NUL-terminated strings (easier). + * + * @param gc PurpleConnection + * @param buf Buffer to send + * @param total_bytes Size of buffer to send + * + * @return Bytes successfully sent, or -1 on error. + */ +static int +msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes) +{ + int total_bytes_sent; + MsimSession *session; + + g_return_val_if_fail(gc != NULL, -1); + g_return_val_if_fail(buf != NULL, -1); + g_return_val_if_fail(total_bytes >= 0, -1); + + session = (MsimSession *)gc->proto_data; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); + + /* Loop until all data is sent, or a failure occurs. */ + total_bytes_sent = 0; + do { + int bytes_sent; + + bytes_sent = send(session->fd, buf + total_bytes_sent, + total_bytes - total_bytes_sent, 0); + + if (bytes_sent < 0) { + purple_debug_info("msim", "msim_send_raw(%s): send() failed: %s\n", + buf, g_strerror(errno)); + return total_bytes_sent; + } + total_bytes_sent += bytes_sent; + + } while(total_bytes_sent < total_bytes); + + return total_bytes_sent; +} + +/** + * Send raw data (given as a NUL-terminated string) to the server. + * + * @param session + * @param msg The raw data to send, in a NUL-terminated string. + * + * @return TRUE if succeeded, FALSE if not. + * + */ +gboolean +msim_send_raw(MsimSession *session, const gchar *msg) +{ + size_t len; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + purple_debug_info("msim", "msim_send_raw: writing <%s>\n", msg); + len = strlen(msg); + + return msim_send_really_raw(session->gc, msg, len) == len; +} + +static GHashTable * +msim_get_account_text_table(PurpleAccount *unused) +{ + GHashTable *table; + + table = g_hash_table_new(g_str_hash, g_str_equal); + + g_hash_table_insert(table, "login_label", (gpointer)_("Email Address...")); + + return table; +} + +/** + * Callbacks called by Purple, to access this plugin. + */ static PurplePluginProtocolInfo prpl_info = { /* options */ OPT_PROTO_USE_POINTSIZE /* specify font size in sane point size */ | OPT_PROTO_MAIL_CHECK, - /* | OPT_PROTO_IM_IMAGE - TODO: direct images. */ + /* | OPT_PROTO_IM_IMAGE - TODO: direct images. */ NULL, /* user_splits */ NULL, /* protocol_options */ NO_BUDDY_ICONS, /* icon_spec - TODO: eventually should add this */ @@ -3152,9 +2927,120 @@ msim_get_account_text_table, /* get_account_text_table */ }; - - -/** Based on MSN's plugin info comments. */ +/** + * Load the plugin. + */ +static gboolean +msim_load(PurplePlugin *plugin) +{ + /* If compiled to use RC4 from libpurple, check if it is really there. */ + if (!purple_ciphers_find_cipher("rc4")) { + purple_debug_error("msim", "rc4 not in libpurple, but it is required - not loading MySpaceIM plugin!\n"); + purple_notify_error(plugin, _("Missing Cipher"), + _("The RC4 cipher could not be found"), + _("Upgrade " + "to a libpurple with RC4 support (>= 2.0.1). MySpaceIM " + "plugin will not be loaded.")); + return FALSE; + } + return TRUE; +} + +/** + * Called when friends have been imported to buddy list on server. + */ +static void +msim_import_friends_cb(MsimSession *session, MsimMessage *reply, gpointer user_data) +{ + MsimMessage *body; + gchar *completed; + msim_msg_dump("msim_import_friends_cb=%s", reply); + + /* Check if the friends were imported successfully. */ + body = msim_msg_get_dictionary(reply, "body"); + g_return_if_fail(body != NULL); + completed = msim_msg_get_string(body, "Completed"); + g_return_if_fail(body != NULL); + msim_msg_free(body); + if (!g_str_equal(completed, "True")) + { + purple_debug_info("msim_import_friends_cb", + "failed to import friends: %s", completed); + purple_notify_error(session->account, _("Add friends from MySpace.com"), + _("Importing friends failed"), NULL); + g_free(completed); + return; + } + g_free(completed); + + purple_debug_info("msim_import_friends_cb", + "added friends to server-side buddy list, requesting new contacts from server"); + + msim_get_contact_list(session, MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS); + + /* TODO: show, X friends have been added */ +} + +/** + * Import friends from myspace.com. + */ +static void msim_import_friends(PurplePluginAction *action) +{ + PurpleConnection *gc; + MsimSession *session; + gchar *group_name; + + gc = (PurpleConnection *)action->context; + session = (MsimSession *)gc->proto_data; + + group_name = "MySpace Friends"; + + g_return_if_fail(msim_send(session, + "persist", MSIM_TYPE_INTEGER, 1, + "sesskey", MSIM_TYPE_INTEGER, session->sesskey, + "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_PUT, + "dsn", MSIM_TYPE_INTEGER, MC_IMPORT_ALL_FRIENDS_DSN, + "lid", MSIM_TYPE_INTEGER, MC_IMPORT_ALL_FRIENDS_LID, + "uid", MSIM_TYPE_INTEGER, session->userid, + "rid", MSIM_TYPE_INTEGER, + msim_new_reply_callback(session, msim_import_friends_cb, NULL), + "body", MSIM_TYPE_STRING, + g_strdup_printf("GroupName=%s", group_name), + NULL)); +} + +/** + * Actions menu for account. + */ +static GList * +msim_actions(PurplePlugin *plugin, gpointer context) +{ + PurpleConnection *gc; + GList *menu; + PurplePluginAction *act; + + gc = (PurpleConnection *)context; + + menu = NULL; + +#if 0 + /* TODO: find out how */ + act = purple_plugin_action_new(_("Find people..."), msim_); + menu = g_list_append(menu, act); + + act = purple_plugin_action_new(_("Change IM name..."), NULL); + menu = g_list_append(menu, act); +#endif + + act = purple_plugin_action_new(_("Add friends from MySpace.com"), msim_import_friends); + menu = g_list_append(menu, act); + + return menu; +} + +/** + * Based on MSN's plugin info comments. + */ static PurplePluginInfo info = { PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, @@ -3188,32 +3074,18 @@ NULL /**< reserved4 */ }; - #ifdef MSIM_SELF_TEST -/** Test functions. +/* + * Test functions. * Used to test or try out the internal workings of msimprpl. If you're reading * this code for the first time, these functions can be instructive in learning * how msimprpl is architected. */ -void -msim_test_all(void) { - guint failures; - - - failures = 0; - failures += msim_test_msg(); - failures += msim_test_escaping(); - - if (failures) { - purple_debug_info("msim", "msim_test_all HAD FAILURES: %d\n", failures); - } else { - purple_debug_info("msim", "msim_test_all - all tests passed!\n"); - } - exit(0); -} - -/** Test MsimMessage for basic functionality. */ -int + +/** + * Test MsimMessage for basic functionality. + */ +static int msim_test_msg(void) { MsimMessage *msg, *msg_cloned, *msg2; @@ -3290,8 +3162,10 @@ return failures; } -/** Test protocol-level escaping/unescaping. */ -int +/** + * Test protocol-level escaping/unescaping. + */ +static int msim_test_escaping(void) { guint failures; @@ -3322,87 +3196,146 @@ return failures; } + +static void +msim_test_all(void) +{ + guint failures; + + failures = 0; + failures += msim_test_msg(); + failures += msim_test_escaping(); + + if (failures) { + purple_debug_info("msim", "msim_test_all HAD FAILURES: %d\n", failures); + } else { + purple_debug_info("msim", "msim_test_all - all tests passed!\n"); + } + exit(0); +} #endif -static gboolean -msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params) +#ifdef MSIM_CHECK_NEWER_VERSION +/** + * Callback for when a currentversion.txt has been downloaded. + */ +static void +msim_check_newer_version_cb(PurpleUtilFetchUrlData *url_data, + gpointer user_data, + const gchar *url_text, + gsize len, + const gchar *error_message) { - PurpleAccount *account; - MsimSession *session; - GList *l; - gchar *uid_str, *cid_str; - guint uid, cid; - - if (g_ascii_strcasecmp(proto, "myim")) - return FALSE; - - /* Parameters are case-insensitive. */ - uid_str = g_hash_table_lookup(params, "uid"); - cid_str = g_hash_table_lookup(params, "cid"); - - uid = uid_str ? atol(uid_str) : 0; - cid = cid_str ? atol(cid_str) : 0; - - /* Need a contact. */ - g_return_val_if_fail(cid != 0, FALSE); - - /* TODO: if auto=true, "Add all the people on this page to my IM List!", on - * http://collect.myspace.com/index.cfm?fuseaction=im.friendslist. Don't need a cid. */ - - /* Convert numeric contact ID back to a string. Needed for looking up. Don't just - * directly use cid directly from parameters, because it might not be numeric. - * It is trivial to change this to allow cID to be a username, but that's not how - * the official MySpaceIM client works, so don't provide that functionality. */ - cid_str = g_strdup_printf("%d", cid); - - - /* Find our account with specified user id, or use first connected account if uid=0. */ - account = NULL; - l = purple_accounts_get_all(); - while (l) { - if (purple_account_is_connected(l->data) && - (uid == 0 || purple_account_get_int(l->data, "uid", 0) == uid)) { - account = l->data; - break; - } - l = l->next; + GKeyFile *keyfile; + GError *error; + GString *data; + gchar *newest_filever; + + if (!url_text) { + purple_debug_info("msim_check_newer_version_cb", + "got error: %s\n", error_message); + return; + } + + purple_debug_info("msim_check_newer_version_cb", + "url_text=%s\n", url_text ? url_text : "(NULL)"); + + /* Prepend [group] so that GKeyFile can parse it (requires a group). */ + data = g_string_new(url_text); + purple_debug_info("msim", "data=%s\n", data->str + ? data->str : "(NULL)"); + data = g_string_prepend(data, "[group]\n"); + + purple_debug_info("msim", "data=%s\n", data->str + ? data->str : "(NULL)"); + + /* url_text is variable=data\n...†*/ + + /* Check FILEVER, 1.0.716.0. 716 is build, MSIM_CLIENT_VERSION */ + /* New (english) version can be downloaded from SETUPURL+SETUPFILE */ + + error = NULL; + keyfile = g_key_file_new(); + + /* Default list seperator is ;, but currentversion.txt doesn't have + * these, so set to an unused character to avoid parsing problems. */ + g_key_file_set_list_separator(keyfile, '\0'); + + g_key_file_load_from_data(keyfile, data->str, data->len, + G_KEY_FILE_NONE, &error); + g_string_free(data, TRUE); + + if (error != NULL) { + purple_debug_info("msim_check_newer_version_cb", + "couldn't parse, error: %d %d %s\n", + error->domain, error->code, error->message); + g_error_free(error); + return; } - if (!account) { - purple_notify_error(NULL, _("myim URL handler"), - _("No suitable MySpaceIM account could be found to open this myim URL."), - _("Enable the proper MySpaceIM account and try again.")); - g_free(cid_str); - return FALSE; + gchar **ks; + guint n; + ks = g_key_file_get_keys(keyfile, "group", &n, NULL); + purple_debug_info("msim", "n=%d\n", n); + guint i; + for (i = 0; ks[i] != NULL; ++i) + { + purple_debug_info("msim", "%d=%s\n", i, ks[i]); + } + + newest_filever = g_key_file_get_string(keyfile, "group", + "FILEVER", &error); + + purple_debug_info("msim_check_newer_version_cb", + "newest filever: %s\n", newest_filever ? + newest_filever : "(NULL)"); + if (error != NULL) { + purple_debug_info("msim_check_newer_version_cb", + "error: %d %d %s\n", + error->domain, error->code, error->message); + g_error_free(error); } - session = (MsimSession *)account->gc->proto_data; - g_return_val_if_fail(session != NULL, FALSE); - - /* Lookup userid to username. TODO: push this down, to IM sending/contact - * adding functions. */ - - /* myim:sendIM?uID=USERID&cID=CONTACTID */ - if (!g_ascii_strcasecmp(cmd, "sendIM")) { - msim_lookup_user(session, cid_str, (MSIM_USER_LOOKUP_CB)msim_uri_handler_sendIM_cb, NULL); - g_free(cid_str); - return TRUE; - - /* myim:addContact?uID=USERID&cID=CONTACTID */ - } else if (!g_ascii_strcasecmp(cmd, "addContact")) { - msim_lookup_user(session, cid_str, (MSIM_USER_LOOKUP_CB)msim_uri_handler_addContact_cb, NULL); - g_free(cid_str); - return TRUE; + g_key_file_free(keyfile); + + exit(0); +} +#endif + +/** + Handle a myim:addContact command, after username has been looked up. + */ +static void +msim_uri_handler_addContact_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) +{ + MsimMessage *body; + gchar *username; + + body = msim_msg_get_dictionary(userinfo, "body"); + username = msim_msg_get_string(body, "UserName"); + msim_msg_free(body); + + if (!username) { + guint uid; + + uid = msim_msg_get_integer(userinfo, "UserID"); + g_return_if_fail(uid != 0); + + username = g_strdup_printf("%d", uid); } - return FALSE; + purple_blist_request_add_buddy(session->account, username, _("Buddies"), NULL); + + g_free(username); } /* TODO: move uid->username resolving to IM sending and buddy adding functions, * so that user can manually add or IM by userid and username automatically * looked up if possible? */ - -/** Handle a myim:sendIM URI command, after username has been looked up. */ + +/** + * Handle a myim:sendIM URI command, after username has been looked up. + */ static void msim_uri_handler_sendIM_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) { @@ -3436,35 +3369,95 @@ g_free(username); } -/** Handle a myim:addContact command, after username has been looked up. */ -static void -msim_uri_handler_addContact_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) +static gboolean +msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params) { - MsimMessage *body; - gchar *username; - - body = msim_msg_get_dictionary(userinfo, "body"); - username = msim_msg_get_string(body, "UserName"); - msim_msg_free(body); - - if (!username) { - guint uid; - - uid = msim_msg_get_integer(userinfo, "UserID"); - g_return_if_fail(uid != 0); - - username = g_strdup_printf("%d", uid); + PurpleAccount *account; + MsimSession *session; + GList *l; + gchar *uid_str, *cid_str; + guint uid, cid; + + if (g_ascii_strcasecmp(proto, "myim")) + return FALSE; + + /* Parameters are case-insensitive. */ + uid_str = g_hash_table_lookup(params, "uid"); + cid_str = g_hash_table_lookup(params, "cid"); + + uid = uid_str ? atol(uid_str) : 0; + cid = cid_str ? atol(cid_str) : 0; + + /* Need a contact. */ + g_return_val_if_fail(cid != 0, FALSE); + + /* TODO: if auto=true, "Add all the people on this page to my IM List!", on + * http://collect.myspace.com/index.cfm?fuseaction=im.friendslist. Don't need a cid. */ + + /* Convert numeric contact ID back to a string. Needed for looking up. Don't just + * directly use cid directly from parameters, because it might not be numeric. + * It is trivial to change this to allow cID to be a username, but that's not how + * the official MySpaceIM client works, so don't provide that functionality. */ + cid_str = g_strdup_printf("%d", cid); + + + /* Find our account with specified user id, or use first connected account if uid=0. */ + account = NULL; + l = purple_accounts_get_all(); + while (l) { + if (purple_account_is_connected(l->data) && + (uid == 0 || purple_account_get_int(l->data, "uid", 0) == uid)) { + account = l->data; + break; + } + l = l->next; } - - purple_blist_request_add_buddy(session->account, username, _("Buddies"), NULL); - - g_free(username); + if (!account) { + purple_notify_error(NULL, _("myim URL handler"), + _("No suitable MySpaceIM account could be found to open this myim URL."), + _("Enable the proper MySpaceIM account and try again.")); + g_free(cid_str); + return FALSE; + } + + session = (MsimSession *)account->gc->proto_data; + g_return_val_if_fail(session != NULL, FALSE); + + /* Lookup userid to username. TODO: push this down, to IM sending/contact + * adding functions. */ + + /* myim:sendIM?uID=USERID&cID=CONTACTID */ + if (!g_ascii_strcasecmp(cmd, "sendIM")) { + msim_lookup_user(session, cid_str, (MSIM_USER_LOOKUP_CB)msim_uri_handler_sendIM_cb, NULL); + g_free(cid_str); + return TRUE; + + /* myim:addContact?uID=USERID&cID=CONTACTID */ + } else if (!g_ascii_strcasecmp(cmd, "addContact")) { + msim_lookup_user(session, cid_str, (MSIM_USER_LOOKUP_CB)msim_uri_handler_addContact_cb, NULL); + g_free(cid_str); + return TRUE; + } + + return FALSE; } -/** Initialize plugin. */ -void -init_plugin(PurplePlugin *plugin) +#ifdef MSIM_DEBUG_MSG +static void +print_hash_item(gpointer key, gpointer value, gpointer user_data) +{ + purple_debug_info("msim", "%s=%s\n", + key ? (gchar *)key : "(NULL)", + value ? (gchar *)value : "(NULL)"); +} +#endif + +/** + * Initialize plugin. + */ +static void +init_plugin(PurplePlugin *plugin) { #ifdef MSIM_SELF_TEST msim_test_all(); @@ -3515,7 +3508,7 @@ #endif /* Code below only runs once. Based on oscar.c's oscar_init(). */ - if (initialized) + if (initialized) return; initialized = TRUE;
--- a/libpurple/protocols/myspace/myspace.h Tue Dec 16 19:17:44 2008 +0000 +++ b/libpurple/protocols/myspace/myspace.h Tue Dec 16 19:18:41 2008 +0000 @@ -69,12 +69,12 @@ /*#define MSIM_DEBUG_LOGIN_CHALLENGE*/ /*#define MSIM_DEBUG_RXBUF */ -/* Encode unknown HTML tags from IM clients in messages as [tag], instead of +/* Encode unknown HTML tags from IM clients in messages as [tag], instead of * ignoring. Useful for debugging */ /*#define MSIM_MARKUP_SHOW_UNKNOWN_TAGS */ /* Define to cause init_plugin() to run some tests and print - * the results to the Purple debug log, then exit. Useful to + * the results to the Purple debug log, then exit. Useful to * run with 'pidgin -d' to see the output. Don't define if * you want to actually use the plugin! */ /*#define MSIM_SELF_TEST */ @@ -119,8 +119,7 @@ #define MSIM_KEEPALIVE_INTERVAL_CHECK (30 * 1000) /* Time to check for new mail (milliseconds) */ -#define MSIM_MAIL_INTERVAL_CHECK (60 * 1000) - +#define MSIM_MAIL_INTERVAL_CHECK (60 * 1000) /* Constants */ #define HASH_SIZE 0x14 /**< Size of SHA-1 hash for login */ @@ -143,7 +142,7 @@ #define MSIM_AUTH_CHALLENGE_LENGTH 0x40 /* TODO: obtain IPs of network interfaces from user's machine, instead of - * hardcoding these values below (used in msim_compute_login_response). + * hardcoding these values below (used in msim_compute_login_response). * This is not immediately * important because you can still connect and perform basic * functions of the protocol. There is also a high chance that the addreses @@ -173,7 +172,6 @@ #define MSIM_STATUS_CODE_IDLE 2 #define MSIM_STATUS_CODE_AWAY 5 - /* Inbox status bitfield values for MsimSession.inbox_status. */ #define MSIM_INBOX_MAIL (1 << 0) #define MSIM_INBOX_BLOG_COMMENT (1 << 1) @@ -191,49 +189,13 @@ #define MSIM_ERROR_LOGGED_IN_ELSEWHERE 6 /* Functions */ -gboolean msim_load(PurplePlugin *plugin); -GList *msim_status_types(PurpleAccount *acct); - -const gchar *msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy); gboolean msim_send_raw(MsimSession *session, const gchar *msg); -void msim_login(PurpleAccount *acct); -int msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, PurpleMessageFlags flags); -unsigned int msim_send_typing(PurpleConnection *gc, const gchar *name, PurpleTypingState state); - -void msim_get_info(PurpleConnection *gc, const gchar *name); - -void msim_set_status(PurpleAccount *account, PurpleStatus *status); -void msim_set_idle(PurpleConnection *gc, int time); - -void msim_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group); -void msim_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group); - -const char *msim_normalize(const PurpleAccount *account, const char *str); - -gboolean msim_offline_message(const PurpleBuddy *buddy); - -void msim_close(PurpleConnection *gc); - -char *msim_status_text(PurpleBuddy *buddy); -void msim_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full); -GList *msim_actions(PurplePlugin *plugin, gpointer context); - -#ifdef MSIM_SELF_TEST -void msim_test_all(void) __attribute__((__noreturn__)); -int msim_test_msg(void); -int msim_test_escaping(void); -#endif - gboolean msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, int type); gboolean msim_we_are_logged_on(MsimSession *session); - void msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note); guint msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb, gpointer data); - -void init_plugin(PurplePlugin *plugin); - #endif /* !_MYSPACE_MYSPACE_H */
--- a/libpurple/protocols/myspace/persist.h Tue Dec 16 19:17:44 2008 +0000 +++ b/libpurple/protocols/myspace/persist.h Tue Dec 16 19:18:41 2008 +0000 @@ -43,7 +43,7 @@ /** Define a set of _DSN and _LID constants for a persistance request. */ #define MSIM_PERSIST_DSN_LID(name,dsn,lid) \ static const int name##_DSN = dsn; \ - static const int name##_LID = lid; + static const int name##_LID = lid; /* Can't do this, errors: * persist.h:51:3: error: '#' is not followed by a macro parameter
--- a/libpurple/protocols/myspace/session.c Tue Dec 16 19:17:44 2008 +0000 +++ b/libpurple/protocols/myspace/session.c Tue Dec 16 19:18:41 2008 +0000 @@ -17,7 +17,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ - #include "myspace.h" /* Session methods */ @@ -47,9 +46,9 @@ session->fd = -1; /* TODO: Remove. */ - session->user_lookup_cb = g_hash_table_new_full(g_direct_hash, + session->user_lookup_cb = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL); /* do NOT free function pointers! (values) */ - session->user_lookup_cb_data = g_hash_table_new_full(g_direct_hash, + session->user_lookup_cb_data = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL);/* TODO: we don't know what the values are, they could be integers inside gpointers or strings, so I don't freed them. @@ -65,7 +64,7 @@ session->last_comm = time(NULL); session->inbox_status = 0; session->inbox_handle = 0; - + return session; } @@ -74,11 +73,11 @@ * * @param session The session to destroy. */ -void +void msim_session_destroy(MsimSession *session) { g_return_if_fail(MSIM_SESSION_VALID(session)); - + session->magic = -1; g_free(session->rxbuf); @@ -91,7 +90,7 @@ if (session->server_info) { msim_msg_free(session->server_info); } - + /* Stop checking the inbox at the end of the session. */ if (session->inbox_handle) { purple_timeout_remove(session->inbox_handle); @@ -99,4 +98,3 @@ g_free(session); } -
--- a/libpurple/protocols/myspace/session.h Tue Dec 16 19:17:44 2008 +0000 +++ b/libpurple/protocols/myspace/session.h Tue Dec 16 19:18:41 2008 +0000 @@ -54,7 +54,6 @@ /* Check if an MsimSession is valid */ #define MSIM_SESSION_VALID(s) (session != NULL && session->magic == MSIM_SESSION_STRUCT_MAGIC) - MsimSession *msim_session_new(PurpleAccount *acct); void msim_session_destroy(MsimSession *session);
--- a/libpurple/protocols/myspace/user.c Tue Dec 16 19:17:44 2008 +0000 +++ b/libpurple/protocols/myspace/user.c Tue Dec 16 19:18:41 2008 +0000 @@ -19,21 +19,13 @@ #include "myspace.h" -static void msim_store_user_info_each(const gchar *key_str, gchar *value_str, MsimUser *user); -static gchar *msim_format_now_playing(const gchar *band, const gchar *song); -static void msim_downloaded_buddy_icon(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, - gsize len, const gchar *error_message); +static void msim_check_username_availability_cb(PurpleConnection *gc, const char *username_to_check); -/* Callbacks for setting the username bit */ -static void msim_check_username_availability_cb(PurpleConnection *gc, const char *value); -static void msim_username_is_available_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); -static void msim_set_username_confirmed_cb(PurpleConnection *gc); -static void msim_username_is_set_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); -static void msim_set_username(MsimSession *session, const gchar *username, - MSIM_USER_LOOKUP_CB cb, gpointer data); static char *msim_username_to_set; -/** Format the "now playing" indicator, showing the artist and song. +/** + * Format the "now playing" indicator, showing the artist and song. + * * @return Return a new string (must be g_free()'d), or NULL. */ static gchar * @@ -47,7 +39,10 @@ return NULL; } } -/** Get the MsimUser from a PurpleBuddy, creating it if needed. */ + +/** + * Get the MsimUser from a PurpleBuddy, creating it if needed. + */ MsimUser * msim_get_user_from_buddy(PurpleBuddy *buddy) { @@ -65,14 +60,16 @@ user->buddy = buddy; user->id = purple_blist_node_get_int(&buddy->node, "UserID"); buddy->proto_data = (gpointer)user; - } + } user = (MsimUser *)(buddy->proto_data); return user; } -/** Find and return an MsimUser * representing a user on the buddy list, or NULL. */ +/** + * Find and return an MsimUser * representing a user on the buddy list, or NULL. + */ MsimUser * msim_find_user(MsimSession *session, const gchar *username) { @@ -89,7 +86,8 @@ return user; } -/** Append user information to a PurpleNotifyUserInfo, given an MsimUser. +/** + * Append user information to a PurpleNotifyUserInfo, given an MsimUser. * Used by msim_tooltip_text() and msim_get_info_cb() to show a user's profile. */ void @@ -99,7 +97,7 @@ gchar *str; guint cv; - /* Useful to identify the account the tooltip refers to. + /* Useful to identify the account the tooltip refers to. * Other prpls show this. */ if (user->username) { purple_notify_user_info_add_pair(user_info, _("User"), user->username); @@ -185,7 +183,39 @@ } } -/** Set the currently playing song artist and or title. +/** + * Callback for when a buddy icon finished being downloaded. + */ +static void +msim_downloaded_buddy_icon(PurpleUtilFetchUrlData *url_data, + gpointer user_data, + const gchar *url_text, + gsize len, + const gchar *error_message) +{ + MsimUser *user; + + user = (MsimUser *)user_data; + + purple_debug_info("msim_downloaded_buddy_icon", + "Downloaded %" G_GSIZE_FORMAT " bytes\n", len); + + if (!url_text) { + purple_debug_info("msim_downloaded_buddy_icon", + "failed to download icon for %s", + user->buddy->name); + return; + } + + purple_buddy_icons_set_for_user(user->buddy->account, + user->buddy->name, + g_memdup((gchar *)url_text, len), len, + /* Use URL itself as buddy icon "checksum" (TODO: ETag) */ + user->image_url); /* checksum */ +} + +/** + * Set the currently playing song artist and or title. * * @param user User associated with the now playing information. * @@ -224,15 +254,15 @@ if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) { PurpleStatus *status; - + status = purple_presence_get_status(presence, "tune"); prev_title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE); prev_artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST); - } + } if (!new_artist) new_artist = prev_artist; - + if (!new_title) new_title = prev_title; @@ -242,14 +272,15 @@ NULL); } -/** Store a field of information about a buddy. +/** + * Store a field of information about a buddy. * * @param key_str Key to store. * @param value_str Value string, either user takes ownership of this string * or it is freed if MsimUser doesn't store the string. * @param user User to store data in. Existing data will be replaced. - * */ -void + */ +static void msim_store_user_info_each(const gchar *key_str, gchar *value_str, MsimUser *user) { if (g_str_equal(key_str, "UserID") || g_str_equal(key_str, "ContactID")) { @@ -286,7 +317,7 @@ const gchar *previous_url; if (user->temporary_user) { - /* This user will be destroyed soon; don't try to look up its image or avatar, + /* This user will be destroyed soon; don't try to look up its image or avatar, * since that won't return immediately and we will end up accessing freed data. */ g_free(value_str); @@ -294,7 +325,7 @@ } if (user->temporary_user) { - /* This user will be destroyed soon; don't try to look up its image or avatar, + /* This user will be destroyed soon; don't try to look up its image or avatar, * since that won't return immediately and we will end up accessing freed data. */ g_free(value_str); @@ -342,7 +373,8 @@ } } -/** Save buddy information to the buddy list from a user info reply message. +/** + * Save buddy information to the buddy list from a user info reply message. * * @param session * @param msg The user information reply, with any amount of information. @@ -354,9 +386,8 @@ * * If the function has no buddy information, this function * is a no-op (and returns FALSE). - * */ -gboolean +gboolean msim_store_user_info(MsimSession *session, MsimMessage *msg, MsimUser *user) { gchar *username; @@ -373,13 +404,13 @@ username = msim_msg_get_string(body, "UserName"); if (!username) { - purple_debug_info("msim", + purple_debug_info("msim", "msim_process_reply: not caching body, no UserName\n"); msim_msg_free(body); g_free(username); return FALSE; } - + /* Null user = find and store in PurpleBuddy's proto_data */ if (!user) { user = msim_find_user(session, username); @@ -391,8 +422,8 @@ } /* TODO: make looping over MsimMessage's easier. */ - for (body_node = body; - body_node != NULL; + for (body_node = body; + body_node != NULL; body_node = msim_msg_get_next_element_node(body_node)) { const gchar *key_str; @@ -410,7 +441,7 @@ msim_msg_get_integer(msg, "lid") == MG_OWN_IM_INFO_LID) { /* TODO: do something with our own IM info, if we need it for some * specific purpose. Otherwise it is available on the buddy list, - * if the user has themselves as their own buddy. + * if the user has themselves as their own buddy. * * However, much of the info is already available in MsimSession, * stored in msim_we_are_logged_on(). */ @@ -425,6 +456,58 @@ return TRUE; } +#if 0 +/** + * Return whether a given username is syntactically valid. + * Note: does not actually check that the user exists. + */ +static gboolean +msim_is_valid_username(const gchar *user) +{ + return !msim_is_userid(user) && /* Not all numeric */ + strlen(user) <= MSIM_MAX_USERNAME_LENGTH + && strspn(user, "0123456789" + "abcdefghijklmnopqrstuvwxyz" + "_" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == strlen(user); +} +#endif + +/** + * Check if a string is a userid (all numeric). + * + * @param user The user id, email, or name. + * + * @return TRUE if is userid, FALSE if not. + */ +gboolean +msim_is_userid(const gchar *user) +{ + g_return_val_if_fail(user != NULL, FALSE); + + return strspn(user, "0123456789") == strlen(user); +} + +/** + * Check if a string is an email address (contains an @). + * + * @param user The user id, email, or name. + * + * @return TRUE if is an email, FALSE if not. + * + * This function is not intended to be used as a generic + * means of validating email addresses, but to distinguish + * between a user represented by an email address from + * other forms of identification. + */ +static gboolean +msim_is_email(const gchar *user) +{ + g_return_val_if_fail(user != NULL, FALSE); + + return strchr(user, '@') != NULL; +} + /** * Asynchronously lookup user information, calling callback when receive result. * @@ -434,7 +517,7 @@ * @param data An arbitray data pointer passed to the callback. */ /* TODO: change to not use callbacks */ -void +void msim_lookup_user(MsimSession *session, const gchar *user, MSIM_USER_LOOKUP_CB cb, gpointer data) { MsimMessage *body; @@ -460,8 +543,8 @@ if (msim_is_userid(user)) { field_name = "UserID"; - dsn = MG_MYSPACE_INFO_BY_ID_DSN; - lid = MG_MYSPACE_INFO_BY_ID_LID; + dsn = MG_MYSPACE_INFO_BY_ID_DSN; + lid = MG_MYSPACE_INFO_BY_ID_LID; } else if (msim_is_email(user)) { field_name = "Email"; dsn = MG_MYSPACE_INFO_BY_STRING_DSN; @@ -486,231 +569,88 @@ "rid", MSIM_TYPE_INTEGER, rid, "body", MSIM_TYPE_DICTIONARY, body, NULL)); -} - - -/** - * Check if a string is a userid (all numeric). - * - * @param user The user id, email, or name. - * - * @return TRUE if is userid, FALSE if not. - */ -gboolean -msim_is_userid(const gchar *user) -{ - g_return_val_if_fail(user != NULL, FALSE); - - return strspn(user, "0123456789") == strlen(user); -} - -/** Return whether a given username is syntactically valid. - * Note: does not actually check that the user exists. */ -gboolean -msim_is_valid_username(const gchar *user) -{ - return !msim_is_userid(user) && /* Not all numeric */ - strlen(user) <= MSIM_MAX_USERNAME_LENGTH - && strspn(user, "0123456789" - "abcdefghijklmnopqrstuvwxyz" - "_" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == strlen(user); } /** - * Check if a string is an email address (contains an @). - * - * @param user The user id, email, or name. - * - * @return TRUE if is an email, FALSE if not. - * - * This function is not intended to be used as a generic - * means of validating email addresses, but to distinguish - * between a user represented by an email address from - * other forms of identification. - */ -gboolean -msim_is_email(const gchar *user) + * Called after username is set. + */ +static void msim_username_is_set_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) { - g_return_val_if_fail(user != NULL, FALSE); - - return strchr(user, '@') != NULL; -} - - -/** Callback for when a buddy icon finished being downloaded. */ -static void -msim_downloaded_buddy_icon(PurpleUtilFetchUrlData *url_data, - gpointer user_data, - const gchar *url_text, - gsize len, - const gchar *error_message) -{ - MsimUser *user; - - user = (MsimUser *)user_data; - - purple_debug_info("msim_downloaded_buddy_icon", - "Downloaded %" G_GSIZE_FORMAT " bytes\n", len); - - if (!url_text) { - purple_debug_info("msim_downloaded_buddy_icon", - "failed to download icon for %s", - user->buddy->name); - return; - } + gchar *username, *errmsg; + MsimMessage *body; - purple_buddy_icons_set_for_user(user->buddy->account, - user->buddy->name, - g_memdup((gchar *)url_text, len), len, - /* Use URL itself as buddy icon "checksum" (TODO: ETag) */ - user->image_url); /* checksum */ -} - -/*** - * If they hit cancel or no at any point in the Setting Username process, we come here. * - * Currently.. We're safe letting them get by without setting it.. Unless we hear otherwise.. * - * So for now, give them a menu.. If this becomes an issue with the Official client.. boot them here */ -void msim_do_not_set_username_cb(PurpleConnection *gc) { - purple_debug_info("msim", "Don't set username"); + guint rid; + gint cmd,dsn,uid,lid,code; + /* \persistr\\cmd\258\dsn\9\uid\204084363\lid\14\rid\369\body\UserName=TheAlbinoRhino1.Code=0\final\ */ - /* Protocol won't log in now without a username set.. Disconnect */ - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("No username set")); -} - -/** They've decided to set a username! Yay! */ -void msim_set_username_cb(PurpleConnection *gc) { - g_return_if_fail(gc != NULL); - purple_debug_info("msim","Set username\n"); - purple_request_input(gc, _("MySpaceIM - Please Set a Username"), - _("Please enter a username to check its availability:"), - NULL, - "", FALSE, FALSE, NULL, - _("OK"), G_CALLBACK(msim_check_username_availability_cb), - _("Cancel"), G_CALLBACK(msim_do_not_set_username_cb), - purple_connection_get_account(gc), - NULL, - NULL, - gc); -} - -/** Once they've submitted their desired new username, - * check if it is available here. */ -static void msim_check_username_availability_cb(PurpleConnection *gc, const char *username_to_check) -{ - MsimMessage *user_msg; - MsimSession *session; - - g_return_if_fail(gc != NULL); - - session = (MsimSession *)gc->proto_data; + purple_debug_info("msim","username_is_set made\n"); g_return_if_fail(MSIM_SESSION_VALID(session)); - purple_debug_info("msim_check_username_availability_cb", "Checking username: %s\n", username_to_check); - - user_msg = msim_msg_new( - "user", MSIM_TYPE_STRING, g_strdup(username_to_check), - NULL); - - /* 25 characters: letters, numbers, underscores */ - /* TODO: VERIFY ABOVE */ - - /* \persist\1\sesskey\288500516\cmd\1\dsn\5\uid\204084363\lid\7\rid\367\body\UserName=Jaywalker\final\ */ - /* Official client uses a standard lookup... So do we! */ - msim_lookup_user(session, username_to_check, msim_username_is_available_cb, user_msg); -} - -/** This is where we do a bit more than merely prompt the user. - * Now we have some real data to tell us the state of their requested username - * \persistr\\cmd\257\dsn\5\uid\204084363\lid\7\rid\367\body\UserName=TheAlbinoRhino1\final\ */ -static void msim_username_is_available_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) -{ - MsimMessage *msg; - gchar *username; - MsimMessage *body; - gint userid; - - purple_debug_info("msim_username_is_available_cb", "Look up username callback made\n"); - - msg = (MsimMessage *)data; - g_return_if_fail(MSIM_SESSION_VALID(session)); - g_return_if_fail(msg != NULL); - - username = msim_msg_get_string(msg, "user"); + msim_msg_dump("username_is_set message is: %s\n", userinfo); + cmd = msim_msg_get_integer(userinfo, "cmd"); + dsn = msim_msg_get_integer(userinfo, "dsn"); + uid = msim_msg_get_integer(userinfo, "uid"); + lid = msim_msg_get_integer(userinfo, "lid"); body = msim_msg_get_dictionary(userinfo, "body"); + errmsg = g_strdup("An error occurred while trying to set the username.\n" + "Please try again, or visit http://editprofile.myspace.com/index.cfm?" + "fuseaction=profile.username to set your username."); if (!body) { - purple_debug_info("msim_username_is_available_cb", "No body for %s?!\n", username); - purple_connection_error_reason(session->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, - "An error occurred while trying to set the username.\n" - "Please try again, or visit http://editprofile.myspace.com/index.cfm?" - "fuseaction=profile.username to set your username."); - return; + purple_debug_info("msim_username_is_set_cb", "No body"); + /* Error: No body! */ + purple_connection_error_reason(session->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, errmsg); } - - userid = msim_msg_get_integer(body, "UserID"); + username = msim_msg_get_string(body, "UserName"); + code = msim_msg_get_integer(body,"Code"); - purple_debug_info("msim_username_is_available_cb", "Returned username is %s and userid is %d\n", username, userid); msim_msg_free(body); - msim_msg_free(msg); + + purple_debug_info("msim_username_is_set_cb", + "cmd = %d, dsn = %d, lid = %d, code = %d, username = %s\n", + cmd, dsn, lid, code, username); - /* The response for a free username will ONLY have the UserName in it.. - * thus making UserID return 0 when we msg_get_integer it */ - if (userid == 0) { - /* This username is currently unused */ - purple_debug_info("msim_username_is_available_cb", "Username available. Prompting to Confirm.\n"); - msim_username_to_set = g_strdup(username); - g_free(username); - purple_request_yes_no(session->gc, - _("MySpaceIM - Username Available"), - _("This username is available. Would you like to set it?"), - _("ONCE SET, THIS CANNOT BE CHANGED!"), - 0, - session->account, - NULL, - NULL, - session->gc, - G_CALLBACK(msim_set_username_confirmed_cb), - G_CALLBACK(msim_do_not_set_username_cb)); + if (cmd == (MSIM_CMD_BIT_REPLY | MSIM_CMD_PUT) + && dsn == MC_SET_USERNAME_DSN + && lid == MC_SET_USERNAME_LID) + { + purple_debug_info("msim_username_is_set_cb", "Proper cmd,dsn,lid for username_is_set!\n"); + purple_debug_info("msim_username_is_set_cb", "Username Set with return code %d\n",code); + if (code == 0) { + /* Good! */ + session->username = username; + msim_we_are_logged_on(session); + } else { + purple_debug_info("msim_username_is_set", "code is %d",code); + /* TODO: what to do here? */ + } + } else if (cmd == (MSIM_CMD_BIT_REPLY | MSIM_CMD_GET) + && dsn == MG_MYSPACE_INFO_BY_STRING_DSN + && lid == MG_MYSPACE_INFO_BY_STRING_LID) { + /* Not quite done... ONE MORE STEP :) */ + rid = msim_new_reply_callback(session, msim_username_is_set_cb, data); + body = msim_msg_new("UserName", MSIM_TYPE_STRING, g_strdup(username), NULL); + if (!msim_send(session, "persist", MSIM_TYPE_INTEGER, 1, + "sesskey", MSIM_TYPE_INTEGER, session->sesskey, + "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_PUT, + "dsn", MSIM_TYPE_INTEGER, MC_SET_USERNAME_DSN, + "uid", MSIM_TYPE_INTEGER, session->userid, + "lid", MSIM_TYPE_INTEGER, MC_SET_USERNAME_LID, + "rid", MSIM_TYPE_INTEGER, rid, + "body", MSIM_TYPE_DICTIONARY, body, + NULL)) { + /* Error! */ + /* Can't set... Disconnect */ + purple_connection_error_reason(session->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, errmsg); + } + } else { - /* Looks like its in use or we have an invalid response */ - purple_debug_info("msim_username_is_available_cb", "Username unavaiable. Prompting for new entry.\n"); - purple_request_input(session->gc, _("MySpaceIM - Please Set a Username"), - _("This username is unavailable."), - _("Please try another username:"), - "", FALSE, FALSE, NULL, - _("OK"), G_CALLBACK(msim_check_username_availability_cb), - _("Cancel"), G_CALLBACK(msim_do_not_set_username_cb), - session->account, - NULL, - NULL, - session->gc); + /* Error! */ + purple_debug_info("msim","username_is_set Error: Invalid cmd/dsn/lid combination"); + purple_connection_error_reason(session->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, errmsg); } -} - -/* They've confirmed that username that was available, Lets make the call to set it */ -static void msim_set_username_confirmed_cb(PurpleConnection *gc) -{ - MsimMessage *user_msg; - MsimSession *session; - - g_return_if_fail(gc != NULL); - - session = (MsimSession *)gc->proto_data; - - g_return_if_fail(MSIM_SESSION_VALID(session)); - - - user_msg = msim_msg_new( - "user", MSIM_TYPE_STRING, g_strdup(msim_username_to_set), - NULL); - - purple_debug_info("msim_set_username_confirmed_cb", "Setting username to %s\n", msim_username_to_set); - - /* Sets our username... keep your fingers crossed :) */ - msim_set_username(session, msim_username_to_set, msim_username_is_set_cb, user_msg); - g_free(msim_username_to_set); + g_free(errmsg); } /** @@ -721,7 +661,7 @@ * @param cb Callback, called with user information when available. * @param data An arbitray data pointer passed to the callback. */ -static void +static void msim_set_username(MsimSession *session, const gchar *username, MSIM_USER_LOOKUP_CB cb, gpointer data) { @@ -765,82 +705,160 @@ NULL)); } -/** Called after username is set. */ -static void msim_username_is_set_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) +/** + * They've confirmed that username that was available, Lets make the call to set it + */ +static void msim_set_username_confirmed_cb(PurpleConnection *gc) { - gchar *username, *errmsg; - MsimMessage *body; + MsimMessage *user_msg; + MsimSession *session; - guint rid; - gint cmd,dsn,uid,lid,code; - /* \persistr\\cmd\258\dsn\9\uid\204084363\lid\14\rid\369\body\UserName=TheAlbinoRhino1.Code=0\final\ */ + g_return_if_fail(gc != NULL); - purple_debug_info("msim","username_is_set made\n"); - + session = (MsimSession *)gc->proto_data; + g_return_if_fail(MSIM_SESSION_VALID(session)); - msim_msg_dump("username_is_set message is: %s\n", userinfo); - cmd = msim_msg_get_integer(userinfo, "cmd"); - dsn = msim_msg_get_integer(userinfo, "dsn"); - uid = msim_msg_get_integer(userinfo, "uid"); - lid = msim_msg_get_integer(userinfo, "lid"); + user_msg = msim_msg_new( + "user", MSIM_TYPE_STRING, g_strdup(msim_username_to_set), + NULL); + + purple_debug_info("msim_set_username_confirmed_cb", "Setting username to %s\n", msim_username_to_set); + + /* Sets our username... keep your fingers crossed :) */ + msim_set_username(session, msim_username_to_set, msim_username_is_set_cb, user_msg); + g_free(msim_username_to_set); +} + +/** + * This is where we do a bit more than merely prompt the user. + * Now we have some real data to tell us the state of their requested username + * \persistr\\cmd\257\dsn\5\uid\204084363\lid\7\rid\367\body\UserName=TheAlbinoRhino1\final\ + */ +static void msim_username_is_available_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) +{ + MsimMessage *msg; + gchar *username; + MsimMessage *body; + gint userid; + + purple_debug_info("msim_username_is_available_cb", "Look up username callback made\n"); + + msg = (MsimMessage *)data; + g_return_if_fail(MSIM_SESSION_VALID(session)); + g_return_if_fail(msg != NULL); + + username = msim_msg_get_string(msg, "user"); body = msim_msg_get_dictionary(userinfo, "body"); - errmsg = g_strdup("An error occurred while trying to set the username.\n" - "Please try again, or visit http://editprofile.myspace.com/index.cfm?" - "fuseaction=profile.username to set your username."); - + if (!body) { - purple_debug_info("msim_username_is_set_cb", "No body"); - /* Error: No body! */ - purple_connection_error_reason(session->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, errmsg); + purple_debug_info("msim_username_is_available_cb", "No body for %s?!\n", username); + purple_connection_error_reason(session->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, + "An error occurred while trying to set the username.\n" + "Please try again, or visit http://editprofile.myspace.com/index.cfm?" + "fuseaction=profile.username to set your username."); + return; } - username = msim_msg_get_string(body, "UserName"); - code = msim_msg_get_integer(body,"Code"); + userid = msim_msg_get_integer(body, "UserID"); + + purple_debug_info("msim_username_is_available_cb", "Returned username is %s and userid is %d\n", username, userid); msim_msg_free(body); - - purple_debug_info("msim_username_is_set_cb", - "cmd = %d, dsn = %d, lid = %d, code = %d, username = %s\n", - cmd, dsn, lid, code, username); + msim_msg_free(msg); - if (cmd == (MSIM_CMD_BIT_REPLY | MSIM_CMD_PUT) - && dsn == MC_SET_USERNAME_DSN - && lid == MC_SET_USERNAME_LID) { - purple_debug_info("msim_username_is_set_cb", "Proper cmd,dsn,lid for username_is_set!\n"); - purple_debug_info("msim_username_is_set_cb", "Username Set with return code %d\n",code); - if (code == 0) { - /* Good! */ - session->username = username; - msim_we_are_logged_on(session); - } else { - purple_debug_info("msim_username_is_set", "code is %d",code); - /* TODO: what to do here? */ - } - } else if (cmd == (MSIM_CMD_BIT_REPLY | MSIM_CMD_GET) - && dsn == MG_MYSPACE_INFO_BY_STRING_DSN - && lid == MG_MYSPACE_INFO_BY_STRING_LID) { - /* Not quite done... ONE MORE STEP :) */ - rid = msim_new_reply_callback(session, msim_username_is_set_cb, data); - body = msim_msg_new("UserName", MSIM_TYPE_STRING, g_strdup(username), NULL); - if (!msim_send(session, "persist", MSIM_TYPE_INTEGER, 1, - "sesskey", MSIM_TYPE_INTEGER, session->sesskey, - "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_PUT, - "dsn", MSIM_TYPE_INTEGER, MC_SET_USERNAME_DSN, - "uid", MSIM_TYPE_INTEGER, session->userid, - "lid", MSIM_TYPE_INTEGER, MC_SET_USERNAME_LID, - "rid", MSIM_TYPE_INTEGER, rid, - "body", MSIM_TYPE_DICTIONARY, body, - NULL)) { - /* Error! */ - /* Can't set... Disconnect */ - purple_connection_error_reason(session->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, errmsg); - } - + /* The response for a free username will ONLY have the UserName in it.. + * thus making UserID return 0 when we msg_get_integer it */ + if (userid == 0) { + /* This username is currently unused */ + purple_debug_info("msim_username_is_available_cb", "Username available. Prompting to Confirm.\n"); + msim_username_to_set = g_strdup(username); + g_free(username); + purple_request_yes_no(session->gc, + _("MySpaceIM - Username Available"), + _("This username is available. Would you like to set it?"), + _("ONCE SET, THIS CANNOT BE CHANGED!"), + 0, + session->account, + NULL, + NULL, + session->gc, + G_CALLBACK(msim_set_username_confirmed_cb), + G_CALLBACK(msim_do_not_set_username_cb)); } else { - /* Error! */ - purple_debug_info("msim","username_is_set Error: Invalid cmd/dsn/lid combination"); - purple_connection_error_reason(session->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, errmsg); + /* Looks like its in use or we have an invalid response */ + purple_debug_info("msim_username_is_available_cb", "Username unavaiable. Prompting for new entry.\n"); + purple_request_input(session->gc, _("MySpaceIM - Please Set a Username"), + _("This username is unavailable."), + _("Please try another username:"), + "", FALSE, FALSE, NULL, + _("OK"), G_CALLBACK(msim_check_username_availability_cb), + _("Cancel"), G_CALLBACK(msim_do_not_set_username_cb), + session->account, + NULL, + NULL, + session->gc); } - g_free(errmsg); +} + +/** + * Once they've submitted their desired new username, + * check if it is available here. + */ +static void msim_check_username_availability_cb(PurpleConnection *gc, const char *username_to_check) +{ + MsimMessage *user_msg; + MsimSession *session; + + g_return_if_fail(gc != NULL); + + session = (MsimSession *)gc->proto_data; + + g_return_if_fail(MSIM_SESSION_VALID(session)); + + purple_debug_info("msim_check_username_availability_cb", "Checking username: %s\n", username_to_check); + + user_msg = msim_msg_new( + "user", MSIM_TYPE_STRING, g_strdup(username_to_check), + NULL); + + /* 25 characters: letters, numbers, underscores */ + /* TODO: VERIFY ABOVE */ + + /* \persist\1\sesskey\288500516\cmd\1\dsn\5\uid\204084363\lid\7\rid\367\body\UserName=Jaywalker\final\ */ + /* Official client uses a standard lookup... So do we! */ + msim_lookup_user(session, username_to_check, msim_username_is_available_cb, user_msg); } + +/*** + * If they hit cancel or no at any point in the Setting Username process, + * we come here. Currently we're safe letting them get by without + * setting it, unless we hear otherwise. So for now give them a menu. + * If this becomes an issue with the official client then boot them here. + */ +void msim_do_not_set_username_cb(PurpleConnection *gc) +{ + purple_debug_info("msim", "Don't set username"); + + /* Protocol won't log in now without a username set.. Disconnect */ + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("No username set")); +} + +/** + * They've decided to set a username! Yay! + */ +void msim_set_username_cb(PurpleConnection *gc) +{ + g_return_if_fail(gc != NULL); + purple_debug_info("msim","Set username\n"); + purple_request_input(gc, _("MySpaceIM - Please Set a Username"), + _("Please enter a username to check its availability:"), + NULL, + "", FALSE, FALSE, NULL, + _("OK"), G_CALLBACK(msim_check_username_availability_cb), + _("Cancel"), G_CALLBACK(msim_do_not_set_username_cb), + purple_connection_get_account(gc), + NULL, + NULL, + gc); +}
--- a/libpurple/protocols/myspace/user.h Tue Dec 16 19:17:44 2008 +0000 +++ b/libpurple/protocols/myspace/user.h Tue Dec 16 19:18:41 2008 +0000 @@ -42,7 +42,7 @@ gboolean temporary_user; } MsimUser; -/* Callback function pointer type for when a user's information is received, +/* Callback function pointer type for when a user's information is received, * initiated from a user lookup. */ typedef void (*MSIM_USER_LOOKUP_CB)(MsimSession *session, MsimMessage *userinfo, gpointer data); @@ -51,8 +51,6 @@ void msim_append_user_info(MsimSession *session, PurpleNotifyUserInfo *user_info, MsimUser *user, gboolean full); gboolean msim_store_user_info(MsimSession *session, MsimMessage *msg, MsimUser *user); gboolean msim_is_userid(const gchar *user); -gboolean msim_is_email(const gchar *user); -gboolean msim_is_valid_username(const gchar *user); void msim_lookup_user(MsimSession *session, const gchar *user, MSIM_USER_LOOKUP_CB cb, gpointer data); void msim_set_username_cb(PurpleConnection *gc); void msim_do_not_set_username_cb(PurpleConnection *gc);
--- a/libpurple/protocols/myspace/zap.c Tue Dec 16 19:17:44 2008 +0000 +++ b/libpurple/protocols/myspace/zap.c Tue Dec 16 19:18:41 2008 +0000 @@ -20,10 +20,6 @@ #include "myspace.h" #include "zap.h" -static gboolean msim_send_zap(MsimSession *session, const gchar *username, guint code); -static void msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr); - - /** Get zap types. */ GList * msim_attention_types(PurpleAccount *acct) @@ -100,6 +96,33 @@ return types; } +/** Send a zap to a user. */ +static gboolean +msim_send_zap(MsimSession *session, const gchar *username, guint code) +{ + gchar *zap_string; + gboolean rc; + + g_return_val_if_fail(session != NULL, FALSE); + g_return_val_if_fail(username != NULL, FALSE); + + /* Construct and send the actual zap command. */ + zap_string = g_strdup_printf("!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", code); + + if (!msim_send_bm(session, username, zap_string, MSIM_BM_ACTION)) { + purple_debug_info("msim_send_zap", + "msim_send_bm failed: zapping %s with %s\n", + username, zap_string); + rc = FALSE; + } else { + rc = TRUE; + } + + g_free(zap_string); + + return rc; +} + /** Send a zap */ gboolean msim_send_attention(PurpleConnection *gc, const gchar *username, guint code) @@ -130,33 +153,6 @@ return TRUE; } -/** Send a zap to a user. */ -static gboolean -msim_send_zap(MsimSession *session, const gchar *username, guint code) -{ - gchar *zap_string; - gboolean rc; - - g_return_val_if_fail(session != NULL, FALSE); - g_return_val_if_fail(username != NULL, FALSE); - - /* Construct and send the actual zap command. */ - zap_string = g_strdup_printf("!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", code); - - if (!msim_send_bm(session, username, zap_string, MSIM_BM_ACTION)) { - purple_debug_info("msim_send_zap_from_menu", "msim_send_bm failed: zapping %s with %s\n", - username, zap_string); - rc = FALSE; - } else { - rc = TRUE; - } - - g_free(zap_string); - - return rc; - -} - /** Zap someone. Callback from msim_blist_node_menu zap menu. */ static void msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr) @@ -248,5 +244,3 @@ return TRUE; } - -
--- a/libpurple/protocols/simple/simple.c Tue Dec 16 19:17:44 2008 +0000 +++ b/libpurple/protocols/simple/simple.c Tue Dec 16 19:18:41 2008 +0000 @@ -1942,7 +1942,8 @@ if (userserver[1] == NULL || userserver[1][0] == '\0') { purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, - _("SIP connect server not specified")); + _("Unable to connect to server. Please enter the " + "address of the server you wish to connect to.")); return; }
--- a/po/fi.po Tue Dec 16 19:17:44 2008 +0000 +++ b/po/fi.po Tue Dec 16 19:18:41 2008 +0000 @@ -10,8 +10,8 @@ msgstr "" "Project-Id-Version: Pidgin\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-12-01 15:52-0800\n" -"PO-Revision-Date: 2008-09-30 19:46+0300\n" +"POT-Creation-Date: 2008-12-16 00:12+0200\n" +"PO-Revision-Date: 2008-12-16 00:16+0200\n" "Last-Translator: Timo Jyrinki <timo.jyrinki@iki.fi>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -4650,6 +4650,19 @@ msgid "Unable to retrieve MSN Address Book" msgstr "MSN-osoitekirjaa ei onnistuttu noutamaan" +#. only notify the user about problems adding to the friends list +#. * maybe we should do something else for other lists, but it probably +#. * won't cause too many problems if we just ignore it +#, c-format +msgid "Unable to add \"%s\"." +msgstr "Ei voi lisätä \"%s\"." + +msgid "Buddy Add error" +msgstr "Tuttavan lisäysvirhe" + +msgid "The username specified does not exist." +msgstr "Syötetty käyttäjänimi ei ole olemassa." + #, c-format msgid "Buddy list synchronization issue in %s (%s)" msgstr "Tuttavien synkronointiongelma käyttäjätilillä %s (%s)" @@ -4971,6 +4984,12 @@ msgid "Page" msgstr "Lähetä" +msgid "Playing a game" +msgstr "Pelaamassa peliä" + +msgid "Working" +msgstr "Tekee töitä" + msgid "Has you" msgstr "Olet hänen listallaan" @@ -5007,6 +5026,12 @@ msgid "Album" msgstr "Levy" +msgid "Game Title" +msgstr "Pelin nimi" + +msgid "Office Title" +msgstr "Kappaleen nimi" + msgid "Set Friendly Name..." msgstr "Aseta tuttavanimi..." @@ -5197,8 +5222,8 @@ "Käyttäjän profiilista ei löytynyt mitään tietoja. Käyttäjää ei " "todennäköisesti ole olemassa." -msgid "Profile URL" -msgstr "Profiilin URL" +msgid "View web profile" +msgstr "Näytä WWW-profiili" #. *< type #. *< ui_requirement @@ -5446,19 +5471,15 @@ msgid "Do you want to delete this buddy from your address book as well?" msgstr "Haluatko poistaa tämän tuttavan myös osoitekirjastasi?" -#. only notify the user about problems adding to the friends list -#. * maybe we should do something else for other lists, but it probably -#. * won't cause too many problems if we just ignore it -#, c-format -msgid "Unable to add \"%s\"." -msgstr "Ei voi lisätä \"%s\"." - msgid "The username specified is invalid." msgstr "Syötetty käyttäjänimi on virheellinen." msgid "This Hotmail account may not be active." msgstr "Tämä Hotmail-tili ei välttämättä ole aktiivinen." +msgid "Profile URL" +msgstr "Profiilin URL" + #. *< type #. *< ui_requirement #. *< flags @@ -5472,18 +5493,12 @@ msgid "MSN Protocol Plugin" msgstr "MSN-yhteyskäytäntöliitännäinen" -msgid "Missing Cipher" -msgstr "Salaus puuttuu" - -msgid "The RC4 cipher could not be found" -msgstr "RC4-salausta ei löydetty" - -msgid "" -"Upgrade to a libpurple with RC4 support (>= 2.0.1). MySpaceIM plugin will " -"not be loaded." -msgstr "" -"Päivitä libpurpleen jossa RC4-tuki (>= 2.0.1). MySpaceIM-liitännäistä ei " -"ladattu." +#, c-format +msgid "No such user: %s" +msgstr "Käyttäjää ei löydy: %s" + +msgid "User lookup" +msgstr "Käyttäjän haku" msgid "Reading challenge" msgstr "Luetaan tunnistushaastetta" @@ -5494,11 +5509,17 @@ msgid "Logging in" msgstr "Kirjaudutaan sisään" -#, c-format -msgid "Connection to server lost (no data received within %d second)" -msgid_plural "Connection to server lost (no data received within %d seconds)" -msgstr[0] "Yhteys palvelimelle katkesi (dataa ei vastaanotettu %d sekunnissa)" -msgstr[1] "Yhteys palvelimelle katkesi (dataa ei vastaanotettu %d sekunnissa)" +msgid "MySpaceIM - No Username Set" +msgstr "MySpaceIM - Käyttäjänimeä ei asetettu" + +msgid "You appear to have no MySpace username." +msgstr "Sinulla ei näytä olevan MySpace-käyttäjänimeä." + +msgid "Would you like to set one now? (Note: THIS CANNOT BE CHANGED!)" +msgstr "Haluatko asettaa sen nyt? (Huom: TÄTÄ EI VOI MUUTTAA)" + +msgid "Lost connection with server" +msgstr "Yhteys palvelimeen katkesi" #. Can't write _()'d strings in array initializers. Workaround. msgid "New mail messages" @@ -5519,14 +5540,25 @@ msgid "MySpace" msgstr "MySpace" -msgid "MySpaceIM - No Username Set" -msgstr "MySpaceIM - Käyttäjänimeä ei asetettu" - -msgid "You appear to have no MySpace username." -msgstr "Sinulla ei näytä olevan MySpace-käyttäjänimeä." - -msgid "Would you like to set one now? (Note: THIS CANNOT BE CHANGED!)" -msgstr "Haluatko asettaa sen nyt? (Huom: TÄTÄ EI VOI MUUTTAA)" +msgid "IM Friends" +msgstr "Pikaviestikaverit" + +#, c-format +msgid "" +"%d buddy was added or updated from the server (including buddies already on " +"the server-side list)" +msgid_plural "" +"%d buddies were added or updated from the server (including buddies already " +"on the server-side list)" +msgstr[0] "" +"%d tuttava lisättiin tai päivitettiin palvelimelta (mukaan lukien jo " +"palvelinpuolen luettelossa olevat)" +msgstr[1] "" +"%d tuttavaa lisättiin tai päivitettiin palvelimelta (mukaan lukien jo " +"palvelinpuolen luettelossa olevat)" + +msgid "Add contacts from server" +msgstr "Lisää tuttavia palvelimelta" #. The session is now set up, ready to be connected. This emits the #. * signedOn signal, so clients can now do anything with msimprpl, and @@ -5553,31 +5585,6 @@ msgid "MySpaceIM Error" msgstr "MySpaceIM-virhe" -msgid "Failed to add buddy" -msgstr "Tuttavan lisääminen epäonnistui" - -msgid "'addbuddy' command failed." -msgstr "\"addbuddy\"-komento epäonnistui." - -msgid "persist command failed" -msgstr "persist-komento epäonnistui" - -#, c-format -msgid "No such user: %s" -msgstr "Käyttäjää ei löydy: %s" - -msgid "User lookup" -msgstr "Käyttäjän haku" - -msgid "Failed to remove buddy" -msgstr "Tuttavaa poistaminen epäonnistui" - -msgid "'delbuddy' command failed" -msgstr "\"delbuddy\"-komento epäonnistui" - -msgid "blocklist command failed" -msgstr "blocklist-komento epäonnistui" - msgid "Invalid input condition" msgstr "Epäkelpo syötetila" @@ -5591,25 +5598,36 @@ msgid "Couldn't connect to host: %s (%d)" msgstr "Yhteyttä isäntään ei voi muodostaa: %s (%d)" -msgid "IM Friends" -msgstr "Pikaviestikaverit" - -#, c-format -msgid "" -"%d buddy was added or updated from the server (including buddies already on " -"the server-side list)" -msgid_plural "" -"%d buddies were added or updated from the server (including buddies already " -"on the server-side list)" -msgstr[0] "" -"%d tuttava lisättiin tai päivitettiin palvelimelta (mukaan lukien jo " -"palvelinpuolen luettelossa olevat)" -msgstr[1] "" -"%d tuttavaa lisättiin tai päivitettiin palvelimelta (mukaan lukien jo " -"palvelinpuolen luettelossa olevat)" - -msgid "Add contacts from server" -msgstr "Lisää tuttavia palvelimelta" +msgid "Failed to add buddy" +msgstr "Tuttavan lisääminen epäonnistui" + +msgid "'addbuddy' command failed." +msgstr "\"addbuddy\"-komento epäonnistui." + +msgid "persist command failed" +msgstr "persist-komento epäonnistui" + +msgid "Failed to remove buddy" +msgstr "Tuttavaa poistaminen epäonnistui" + +msgid "'delbuddy' command failed" +msgstr "\"delbuddy\"-komento epäonnistui" + +msgid "blocklist command failed" +msgstr "blocklist-komento epäonnistui" + +msgid "Missing Cipher" +msgstr "Salaus puuttuu" + +msgid "The RC4 cipher could not be found" +msgstr "RC4-salausta ei löydetty" + +msgid "" +"Upgrade to a libpurple with RC4 support (>= 2.0.1). MySpaceIM plugin will " +"not be loaded." +msgstr "" +"Päivitä libpurpleen jossa RC4-tuki (>= 2.0.1). MySpaceIM-liitännäistä ei " +"ladattu." msgid "Add friends from MySpace.com" msgstr "Lisää kavereita MySpacesta" @@ -5651,9 +5669,6 @@ msgid "User" msgstr "Käyttäjä" -msgid "Profile" -msgstr "Profiili" - msgid "Headline" msgstr "Otsikko" @@ -5666,16 +5681,6 @@ msgid "Client Version" msgstr "Asiakasohjelman versio" -#. Protocol won't log in now without a username set.. Disconnect -msgid "No username set" -msgstr "Käyttäjänimeä ei asetettu" - -msgid "MySpaceIM - Please Set a Username" -msgstr "MySpaceIM - Aseta käyttäjänimi" - -msgid "Please enter a username to check its availability:" -msgstr "Syötä käyttäjänimi tarkistaaksesi sen saatavuus:" - msgid "MySpaceIM - Username Available" msgstr "MySpaceIM - Käyttäjänimi saatavilla" @@ -5685,12 +5690,22 @@ msgid "ONCE SET, THIS CANNOT BE CHANGED!" msgstr "KUN TÄMÄ ON KERRAN ASETETTU, SITÄ EI VOI MUUTTAA" +msgid "MySpaceIM - Please Set a Username" +msgstr "MySpaceIM - Aseta käyttäjänimi" + msgid "This username is unavailable." msgstr "Tämä käyttäjänimi ei ole saatavilla." msgid "Please try another username:" msgstr "Yritä toista käyttäjänimeä:" +#. Protocol won't log in now without a username set.. Disconnect +msgid "No username set" +msgstr "Käyttäjänimeä ei asetettu" + +msgid "Please enter a username to check its availability:" +msgstr "Syötä käyttäjänimi tarkistaaksesi sen saatavuus:" + #. TODO: icons for each zap #. Lots of comments for translators: #. Zap means "to strike suddenly and forcefully as if with a @@ -6631,6 +6646,9 @@ msgid "Member Since" msgstr "Rekisteröitynyt" +msgid "Profile" +msgstr "Profiili" + msgid "Your AIM connection may be lost." msgstr "AIM-yhteytesi saattaa olla katkennut." @@ -6819,13 +6837,11 @@ "tulee olla oikea sähköpostiosoite, tai alkaa kirjaimella ja sisältää vain " "kirjaimia, numeroita ja välilyöntejä, tai sisältää vain numeroita." -#, fuzzy msgid "Unable to Add" msgstr "Lisääminen epäonnistui" -#, fuzzy msgid "Unable to Retrieve Buddy List" -msgstr "Tuttavien nouto ei onnistunut" +msgstr "Tuttavien noutaminen ei onnistunut" msgid "" "The AIM servers were temporarily unable to send your buddy list. Your buddy " @@ -7123,16 +7139,14 @@ msgid "Other" msgstr "Muu" -#, fuzzy msgid "Visible" -msgstr "Näkymätön" - -msgid "Firend Only" -msgstr "" - -#, fuzzy +msgstr "Näkyvissä" + +msgid "Friend Only" +msgstr "Vain kaverit" + msgid "Private" -msgstr "Yksityisyys" +msgstr "Yksityinen" msgid "QQ Number" msgstr "QQ-numero" @@ -7149,9 +7163,8 @@ msgid "Phone Number" msgstr "Puhelinnumero" -#, fuzzy msgid "Authorize adding" -msgstr "Valtuuta tuttava?" +msgstr "Valtuuta lisäys" msgid "Cellphone Number" msgstr "Matkapuhelinnumero" @@ -7159,100 +7172,82 @@ msgid "Personal Introduction" msgstr "Henkilökohtainen esittely" -#, fuzzy msgid "City/Area" -msgstr "Paikkakunta" - -#, fuzzy +msgstr "Paikkakunta/alue" + msgid "Publish Mobile" -msgstr "Oma matkapuhelin" - -#, fuzzy +msgstr "Julkaise matkapuhelin" + msgid "Publish Contact" -msgstr "Anna kontaktiryhmälle lempinimi" +msgstr "Julkaise yhteystiedot" msgid "College" msgstr "Yliopisto" -#, fuzzy msgid "Horoscope" msgstr "Horoskooppimerkki" -#, fuzzy msgid "Zodiac" msgstr "Eläinradan merkki" -#, fuzzy msgid "Blood" -msgstr "Estetty" - -#, fuzzy +msgstr "Veriryhmä" + msgid "True" -msgstr "härkä" - -#, fuzzy +msgstr "Tosi" + msgid "False" -msgstr "Epäonnistunut" - -#, fuzzy +msgstr "Epätosi" + msgid "Modify Contact" -msgstr "Muokkaa tiliä" - -#, fuzzy +msgstr "Muokkaa yhteystietoa" + msgid "Modify Address" -msgstr "Kotiosoite" - -#, fuzzy +msgstr "Muokkaa osoitetta" + msgid "Modify Extended Information" -msgstr "Muokkaa tietojani" - -#, fuzzy +msgstr "Muokkaa lisätietoja" + msgid "Modify Information" msgstr "Muokkaa tietoja" msgid "Update" msgstr "Päivitä" -#, fuzzy msgid "Could not change buddy information." -msgstr "Muuta tuttavan tietoja." - -#, c-format -msgid "%d needs Q&A" -msgstr "" - -#, fuzzy -msgid "Add buddy Q&A" -msgstr "Lisää tuttava" - -#, fuzzy -msgid "Input answer here" -msgstr "Anna syy tähän" +msgstr "Tuttavan tietojen muuttaminen ei onnistunut." + +#, c-format +msgid "%u requires verification" +msgstr "%u pyytää valtuutusta" + +msgid "Add buddy question" +msgstr "Lisää tuttavakysymys" + +msgid "Enter answer here" +msgstr "Syötä vastaus tähän" msgid "Send" msgstr "Lähetä" -#, fuzzy msgid "Invalid answer." -msgstr "Virheellinen salasana" +msgstr "Virheellinen vastaus." msgid "Authorization denied message:" msgstr "Valtuutuksen eväysviesti:" -#, fuzzy -msgid "Sorry, You are not my style." -msgstr "Pahoittelut, en ole kiinnostunut..." - -#, c-format -msgid "%d needs authentication" -msgstr "Käyttäjä %d tarvitsee valtuutuksen" - -#, fuzzy +msgid "Sorry, you're not my style." +msgstr "Pahoittelut, et ole tyyliäni." + +#, c-format +msgid "%u needs authorization" +msgstr "%u tarvitsee valtuutuksen" + msgid "Add buddy authorize" -msgstr "Lisää tuttava tuttavaluetteloon?" - -msgid "Input request here" -msgstr "Anna syy tähän" +msgstr "Lisää tuttavavaltuutus" + +msgid "Enter request here" +msgstr "Syötä pyyntö tähän" msgid "Would you be my friend?" msgstr "Haluaisitko olla kaverini?" @@ -7260,28 +7255,25 @@ msgid "QQ Buddy" msgstr "QQ-tuttava" -#, fuzzy msgid "Add buddy" msgstr "Lisää tuttava" msgid "Invalid QQ Number" msgstr "Epäkelpo QQ-numero" -#, fuzzy msgid "Failed sending authorize" -msgstr "Voisitko valtuuttaa minut?" - -#, fuzzy, c-format -msgid "Failed removing buddy %d" -msgstr "Tuttavaa poistaminen epäonnistui" - -#, fuzzy, c-format +msgstr "Valtuutuksen lähetys epäonnistui" + +#, c-format +msgid "Failed removing buddy %u" +msgstr "Tuttavan %u poistaminen epäonnistui" + +#, c-format msgid "Failed removing me from %d's buddy list" -msgstr "Poista toisen tuttavista" - -#, fuzzy +msgstr "%d:n tuttavalistalta poistuminen epäonnistui" + msgid "No reason given" -msgstr "Syytä ei annettu." +msgstr "Syytä ei annettu" #. only need to get value #, c-format @@ -7291,7 +7283,7 @@ msgid "Would you like to add him?" msgstr "Haluatko lisätä hänet?" -#, fuzzy, c-format +#, c-format msgid "Rejected by %s" msgstr "%s hylkäsi pyynnön" @@ -7314,59 +7306,55 @@ msgid "You can only search for permanent Qun\n" msgstr "Voit etsiä vain pysyviä Quneja\n" -#, fuzzy +msgid "(Invalid UTF-8 string)" +msgstr "(Viallinen UTF-8-merkkijono)" + msgid "Not member" -msgstr "En ole jäsen" +msgstr "Ei jäsen" msgid "Member" msgstr "Jäsen" -#, fuzzy msgid "Requesting" -msgstr "Pyyntövalintaikkuna" - -#, fuzzy +msgstr "Pyydetään" + msgid "Admin" -msgstr "Ylläpitäjän hälytys" - -#, fuzzy +msgstr "Ylläpitäjä" + msgid "Notice" -msgstr "Huomautus:" - -#, fuzzy +msgstr "Huomautus" + msgid "Detail" msgstr "Yksityiskohdat" msgid "Creator" msgstr "Luoja" -#, fuzzy msgid "About me" -msgstr "Tietoja %sistä" - -#, fuzzy +msgstr "Omat tiedot" + msgid "Category" -msgstr "Keskusteluvirhe" - -#, fuzzy +msgstr "Luokka" + msgid "The Qun does not allow others to join" -msgstr "Tämä ryhmä ei salli muiden liittyä" - -#, fuzzy +msgstr "Tämä Qun ei salli muiden liittyä" + msgid "Join QQ Qun" -msgstr "Liity ryhmäkeskusteluun" - -#, c-format -msgid "Successfully joined Qun %s (%d)" -msgstr "" - -#, fuzzy +msgstr "Liity QQ Quniin" + +msgid "Input request here" +msgstr "Anna syy tähän" + +#, c-format +msgid "Successfully joined Qun %s (%u)" +msgstr "Liityttiin onnistuneesti Quniin %s (%u)" + msgid "Successfully joined Qun" -msgstr "Qun-jäsentä muokattu onnistuneesti" - -#, c-format -msgid "Qun %d denied to join" -msgstr "Qun %d kielsi liittymisen" +msgstr "Liityttiin onnistuneesti Quniin" + +#, c-format +msgid "Qun %u denied from joining" +msgstr "Qun %u kielsi liittymisen" msgid "QQ Qun Operation" msgstr "QQ Qun -toiminta" @@ -7374,12 +7362,11 @@ msgid "Failed:" msgstr "Epäonnistui:" -msgid "Join Qun, Unknow Reply" +msgid "Join Qun, Unknown Reply" msgstr "Quniin liittyminen, tuntematon vastaus" -#, fuzzy msgid "Quit Qun" -msgstr "QQ Qun" +msgstr "Poistu Qunista" msgid "" "Note, if you are the creator, \n" @@ -7388,51 +7375,47 @@ "Huomaa, että olet samalla Qunin luoja, \n" "Tämä toiminto poistaa lopulta tämän Qunin." -#, fuzzy -msgid "Sorry, you are not our style ..." -msgstr "Pahoittelut, en ole kiinnostunut..." - -#, fuzzy -msgid "Successfully changed Qun member" -msgstr "Qun-jäsentä muokattu onnistuneesti" - -#, fuzzy +msgid "Sorry, you are not our style" +msgstr "Pahoittelut, et ole tyyliämme" + +msgid "Successfully changed Qun members" +msgstr "Qun-jäseniä muokattu onnistuneesti" + msgid "Successfully changed Qun information" msgstr "Qun-tietojen muokkaus onnistui" msgid "You have successfully created a Qun" msgstr "Qun:n luonti onnistui" -#, fuzzy -msgid "Would you like to set detailed information now?" -msgstr "Haluatko asettaa Qun:n yksityiskohdat nyt?" +msgid "Would you like to set up detailed information now?" +msgstr "Haluatko asettaa yksityiskohtaiset tiedot nyt?" msgid "Setup" msgstr "Asetukset" -#, fuzzy, c-format -msgid "%d requested to join Qun %d for %s" -msgstr "%d pyysi liittymään Quniin %d" - -#, c-format -msgid "%d request to join Qun %d" -msgstr "%d pyysi liittymään Quniin %d" - -#, fuzzy, c-format -msgid "Failed to join Qun %d, operated by admin %d" -msgstr "Liittyminen tuttavan seuraan keskusteluhuoneeseen epäonnistui" - -#, c-format -msgid "<b>Joining Qun %d is approved by admin %d for %s</b>" -msgstr "" - -#, fuzzy, c-format -msgid "<b>Removed buddy %d.</b>" -msgstr "Poista tuttava" - -#, c-format -msgid "<b>New buddy %d joined.</b>" -msgstr "" +#, c-format +msgid "%u requested to join Qun %u for %s" +msgstr "%u pyysi liittymään Quniin %u: %s" + +#, c-format +msgid "%u request to join Qun %u" +msgstr "%u pyysi liittymään Quniin %u" + +#, c-format +msgid "Failed to join Qun %u, operated by admin %u" +msgstr "Liittyminen Quniin %u epäonnistui, ylläpitäjä %u" + +#, c-format +msgid "<b>Joining Qun %u is approved by admin %u for %s</b>" +msgstr "<b>Quniin %u liittyminen hyväksyttiin ylläpitäjän %u toimesta: %s</b>" + +#, c-format +msgid "<b>Removed buddy %u.</b>" +msgstr "<b>Poistettiin tuttava %u.</b>" + +#, c-format +msgid "<b>New buddy %u joined.</b>" +msgstr "<b>Uusi tuttava %u liittyi.</b>" #, c-format msgid "Unknown-%d" @@ -7468,37 +7451,36 @@ msgid "Invalid name" msgstr "Epäkelpo nimi" -#, fuzzy msgid "Select icon..." -msgstr "Valitse kansio..." - -#, fuzzy, c-format +msgstr "Valitse kuvake..." + +#, c-format msgid "<b>Login time</b>: %d-%d-%d, %d:%d:%d<br>\n" -msgstr "<b>Kirjautumisaika</b>: %s<br>\n" - -#, fuzzy, c-format +msgstr "<b>Kirjautumisaika</b>: %d-%d-%d, %d:%d:%d<br>\n" + +#, c-format msgid "<b>Total Online Buddies</b>: %d<br>\n" -msgstr "<b>Parhaillaan kirjautuneena</b>: %d<br>\n" - -#, fuzzy, c-format +msgstr "<b>Yhteensä tuttavia linjoilla</b>: %d<br>\n" + +#, c-format msgid "<b>Last Refresh</b>: %d-%d-%d, %d:%d:%d<br>\n" -msgstr "<b>Viimeisin päivitys:</b> %s<br>\n" +msgstr "<b>Viimeisin päivitys</b>: %d-%d-%d, %d:%d:%d<br>\n" #, c-format msgid "<b>Server</b>: %s<br>\n" msgstr "<b>Palvelin:</b>: %s<br>\n" -#, fuzzy, c-format +#, c-format msgid "<b>Client Tag</b>: %s<br>\n" -msgstr "<b>Kirjautumisaika</b>: %s<br>\n" +msgstr "<b>Asiakasmerkintä</b>: %s<br>\n" #, c-format msgid "<b>Connection Mode</b>: %s<br>\n" msgstr "<b>Yhteystila</b>: %s<br>\n" -#, fuzzy, c-format +#, c-format msgid "<b>My Internet IP</b>: %s:%d<br>\n" -msgstr "<b>Internet-osoitteeni:</b> %s<br>\n" +msgstr "<b>Internet-IP-osoitteeni</b>: %s:%d<br>\n" #, c-format msgid "<b>Sent</b>: %lu<br>\n" @@ -7520,45 +7502,41 @@ msgid "<b>Received Duplicate</b>: %lu<br>\n" msgstr "<b>Vastaanotettiin moneen kertaan</b>: %lu<br>\n" -#, fuzzy, c-format +#, c-format msgid "<b>Time</b>: %d-%d-%d, %d:%d:%d<br>\n" -msgstr "<b>Kirjautumisaika</b>: %s<br>\n" - -#, fuzzy, c-format +msgstr "<b>Aika</b>: %d-%d-%d, %d:%d:%d<br>\n" + +#, c-format msgid "<b>IP</b>: %s<br>\n" -msgstr "<b>Palvelin:</b>: %s<br>\n" +msgstr "<b>IP</b>: %s<br>\n" msgid "Login Information" msgstr "Kirjautumistietoja" -#, fuzzy msgid "<p><b>Original Author</b>:<br>\n" -msgstr "<b>Ulkoinen käyttäjä</b><br>" +msgstr "<p><b>Alkuperäinen tekijä</b>:<br>\n" msgid "<p><b>Code Contributors</b>:<br>\n" -msgstr "" - -#, fuzzy +msgstr "<p><b>Koodin tuottajat</b>:<br>\n" + msgid "<p><b>Lovely Patch Writers</b>:<br>\n" -msgstr "<b>Viimeisin päivitys:</b> %s<br>\n" - -#, fuzzy +msgstr "<p><b>Ihanat päivitysten kirjoittajat</b>:<br>\n" + msgid "<p><b>Acknowledgement</b>:<br>\n" -msgstr "<b>Lähetetty</b>: %lu<br>\n" +msgstr "<p><b>Tunnustus</b>:<br>\n" msgid "<p><i>And, all the boys in the backroom...</i><br>\n" -msgstr "" +msgstr "<p><i>Ja, kaikki pojat takahuoneessa...</i><br>\n" msgid "<i>Feel free to join us!</i> :)" -msgstr "" - -#, fuzzy, c-format -msgid "About OpenQ r%s" -msgstr "Tietoja %sistä" - -#, fuzzy +msgstr "<i>Liity vapaasti joukkoomme!</i> :)" + +#, c-format +msgid "About OpenQ %s" +msgstr "Tietoja OpenQ %s:sta" + msgid "Change Icon" -msgstr "Tallenna kuvake" +msgstr "Vaihda kuvake" msgid "Change Password" msgstr "Vaihda salasana" @@ -7567,11 +7545,10 @@ msgstr "Tilin tiedot" msgid "Update all QQ Quns" -msgstr "" - -#, fuzzy +msgstr "Päivitä kaikki QQ Qunit" + msgid "About OpenQ" -msgstr "Tietoja %sistä" +msgstr "Tietoja OpenQ:sta" #. *< type #. *< ui_requirement @@ -7583,27 +7560,24 @@ #. *< version #. * summary #. * description -#, fuzzy msgid "QQ Protocol Plugin" msgstr "QQ-yhteyskäytäntöliitännäinen" msgid "Auto" msgstr "Auto" -#, fuzzy msgid "Select Server" -msgstr "Valitse käyttäjä" +msgstr "Valitse palvelin" msgid "QQ2005" -msgstr "" +msgstr "QQ2005" msgid "QQ2007" -msgstr "" +msgstr "QQ2007" msgid "QQ2008" -msgstr "" - -#. #endif +msgstr "QQ2008" + msgid "Connect by TCP" msgstr "Yhdistetään käyttäen TCP:tä" @@ -7613,25 +7587,18 @@ msgid "Show server news" msgstr "Näytä palvelinuutiset" -#, fuzzy msgid "Keep alive interval (seconds)" -msgstr "Jatkuvan yhteydenpidon aikaväli (s)" - -#, fuzzy +msgstr "Jatkuvan yhteydenpidon aikaväli (sekunneissa)" + msgid "Update interval (seconds)" -msgstr "Päivitysten aikaväli (s)" - -#, fuzzy -msgid "Can not decrypt server reply" -msgstr "Kirjautumisvastauksen salausta ei voi purkaa" - -#, fuzzy -msgid "Can not decrypt get server reply" -msgstr "Kirjautumisvastauksen salausta ei voi purkaa" +msgstr "Päivitysten aikaväli (sekunneissa)" + +msgid "Cannot decrypt server reply" +msgstr "Palvelinvastauksen salausta ei voi purkaa" #, c-format msgid "Failed requesting token, 0x%02X" -msgstr "" +msgstr "Poletin pyyntö epäonnistui, 0x%02X" #, c-format msgid "Invalid token len, %d" @@ -7639,56 +7606,53 @@ #. extend redirect used in QQ2006 msgid "Redirect_EX is not currently supported" -msgstr "" +msgstr "Redirect_EX ei ole tällä hetkellä tuettuna" #. need activation #. need activation #. need activation -#, fuzzy msgid "Activation required" -msgstr "Vaatii rekisteröinnin" - -#, fuzzy, c-format -msgid "Unknow reply code when login (0x%02X)" -msgstr "Epäkelpo poletin vastauskoodi, 0x%02X" - -msgid "Keep alive error" -msgstr "Jatkuvan yhteydenpidon virhe" - -#, fuzzy -msgid "Requesting captcha ..." -msgstr "Pyydetään käyttäjän %s huomiota..." - -msgid "Checking code of captcha ..." -msgstr "" - -msgid "Failed captcha verify" -msgstr "" - -#, fuzzy +msgstr "Vaatii aktivoinnin" + +#, c-format +msgid "Unknown reply code when logging in (0x%02X)" +msgstr "Tuntematon vastauskoodi kirjauduttaessa (0x%02X)" + +msgid "Could not decrypt server reply" +msgstr "Palvelinvastauksen salausta ei voi purkaa" + +msgid "Requesting captcha" +msgstr "Pyydetään captchaa" + +msgid "Checking captcha" +msgstr "Tarkistetaan captchaa" + +msgid "Failed captcha verification" +msgstr "Captchan tarkistus epäonnistui" + msgid "Captcha Image" -msgstr "Tallenna kuva" - -#, fuzzy +msgstr "Captcha-kuva" + msgid "Enter code" -msgstr "Anna salasana" - -msgid "QQ Captcha Verifing" -msgstr "" - -#, fuzzy +msgstr "Syötä koodi" + +msgid "QQ Captcha Verification" +msgstr "QQ-Captcha-tarkistus" + msgid "Enter the text from the image" -msgstr "Anna ryhmän nimi" - -#, c-format -msgid "Unknow reply code when checking password (0x%02X)" -msgstr "" - -#, c-format -msgid "" -"Unknow reply code when login (0x%02X):\n" +msgstr "Syötä teksti kuvasta" + +#, c-format +msgid "Unknown reply when checking password (0x%02X)" +msgstr "Tuntematon vastaus tarkistettaessa salasanaa (0x%02X)" + +#, c-format +msgid "" +"Unknown reply code when logging in (0x%02X):\n" "%s" msgstr "" +"Tuntematon vastauskoosi kirjauduttaessa sisään (0x%02X):\n" +"%s" #. we didn't successfully connect. tdt->toc_fd is valid here msgid "Unable to connect." @@ -7697,14 +7661,6 @@ msgid "Socket error" msgstr "Pistokevirhe" -#, c-format -msgid "" -"Lost connection with server:\n" -"%d, %s" -msgstr "" -"Yhteys palvelimeen katkesi:\n" -"%d, %s" - msgid "Unable to read from socket" msgstr "Ei kyetty lukemaan pistoketta" @@ -7714,12 +7670,11 @@ msgid "Connection lost" msgstr "Yhteys katkesi" -#, fuzzy -msgid "Get server ..." -msgstr "Aseta käyttäjätiedot..." - -msgid "Request token" -msgstr "Pyydä poletti" +msgid "Getting server" +msgstr "Haetaan palvelinta" + +msgid "Requesting token" +msgstr "Pyydetään polettia" msgid "Couldn't resolve host" msgstr "Yhteyttä isäntään ei voi löytää" @@ -7727,63 +7682,62 @@ msgid "Invalid server or port" msgstr "Epäkelpo palvelin tai portti" -#, fuzzy -msgid "Connecting server ..." -msgstr "Yhdistä palvelimeen" +msgid "Connecting to server" +msgstr "Yhdistetään palvelimelle" msgid "QQ Error" msgstr "QQ-virhe" -# c-format -msgid "Failed to send IM." -msgstr "Viestin lähettäminen epäonnistui." - -#, fuzzy, c-format +#, c-format msgid "" "Server News:\n" "%s\n" "%s\n" "%s" -msgstr "QQ-palvelimen uutisia" +msgstr "" +"Palvelimen uutisia:\n" +"%s\n" +"%s\n" +"%s" + +#, c-format +msgid "%s:%s" +msgstr "%s:%s" #, c-format msgid "From %s:" msgstr "Lähettäjä %s:" -#, fuzzy, c-format +#, c-format msgid "" "Server notice From %s: \n" "%s" -msgstr "Palvelimen ohjeet: %s" - -msgid "Unknow SERVER CMD" +msgstr "" +"Palvelinilmoitus käyttäjältä %s: \n" +"%s" + +msgid "Unknown SERVER CMD" msgstr "Tuntematon SERVER CMD" #, c-format msgid "" "Error reply of %s(0x%02X)\n" -"Room %d, reply 0x%02X" -msgstr "" -"Virheellinen vastaus kohteesta %s(0x%02X)\n" -"Huone %d, vastaus 0x%02X" +"Room %u, reply 0x%02X" +msgstr "" +"Virheellinen vastaus %s(0x%02X)\n" +"Huone %u, vastaus 0x%02X" msgid "QQ Qun Command" msgstr "QQ-Qun-komento" -#, fuzzy, c-format -msgid "Not a member of room \"%s\"\n" -msgstr "Sinä et ole ryhmän \"%s\" jäsen\n" - -msgid "Can not decrypt login reply" +msgid "Could not decrypt login reply" msgstr "Kirjautumisvastauksen salausta ei voi purkaa" -#, fuzzy -msgid "Unknow LOGIN CMD" -msgstr "Tuntematon vastaus-CMD" - -#, fuzzy -msgid "Unknow CLIENT CMD" -msgstr "Tuntematon SERVER CMD" +msgid "Unknown LOGIN CMD" +msgstr "Tuntematon LOGIN CMD" + +msgid "Unknown CLIENT CMD" +msgstr "Tuntematon CLIENT CMD" #, c-format msgid "%d has declined the file %s" @@ -9746,13 +9700,8 @@ msgid "Last Update" msgstr "Edellinen päivitys" -#, c-format -msgid "User information for %s unavailable" -msgstr "%s:n käyttäjätiedot eivät ole saatavilla" - -msgid "" -"Sorry, this profile seems to be in a language or format that is not " -"supported at this time." +msgid "" +"This profile is in a language or format that is not supported at this time." msgstr "" "Tämä profiili näyttää käyttävän kieltä tai muotoa jota ei tueta tällä " "hetkellä." @@ -10363,7 +10312,7 @@ msgid "Protocol" msgstr "Yhteyskäytäntö" -#, fuzzy, c-format +#, c-format msgid "" "<span size='larger' weight='bold'>Welcome to %s!</span>\n" "\n" @@ -10377,10 +10326,10 @@ msgstr "" "<span size='larger' weight='bold'>Tervetuloa %siin!</span>\n" "\n" -"Pikaviestintilejä ei ole määritelty. Yhdistääksesi %sillä napsauta <b>Lisää</" -"b>-painiketta ja määritä ensimmäisen käyttäjätilisi tiedot. Jos haluat %sin " -"yhdistävän useampiin pikaviestintileihin, napsauta uudestaan <b>Lisää</b>-" -"painiketta määritelläksesi ne kaikki.\n" +"Pikaviestintilejä ei ole määritelty. Yhdistääksesi %sillä napsauta " +"<b>Lisää...</b>-painiketta ja määritä ensimmäisen käyttäjätilisi tiedot. Jos " +"haluat %sin yhdistävän useampiin pikaviestintileihin, napsauta uudelleen " +"<b>Lisää...</b>-painiketta määritelläksesi ne kaikki.\n" "\n" "Voit palata tähän ikkunaan lisäämään, muokkaamaan tai poistamaan tilejä " "valitsemalla <b>Käyttäjätilit->Tilien hallinta</b> Tuttavat-ikkunassa." @@ -10805,9 +10754,8 @@ msgid "Auto_join when account becomes online." msgstr "Liity automaattisesti kun käyttä_jätili pääsee linjoille." -#, fuzzy msgid "_Remain in chat after window is closed." -msgstr "Piilota ry_hmäkeskustelu kun ikkuna on suljettu." +msgstr "Pysy _ryhmäkeskustelussa ikkunan sulkemisen jälkeen." msgid "Please enter the name of the group to be added." msgstr "Anna lisättävän ryhmän nimi." @@ -11181,11 +11129,10 @@ msgstr "Vakavat virheet" msgid "bug master" -msgstr "" - -#, fuzzy +msgstr "ohjelmavirheiden hallitsija" + msgid "artist" -msgstr "Esittäjä" +msgstr "esittäjä" #. feel free to not translate this msgid "Ka-Hing Cheung" @@ -11194,9 +11141,8 @@ msgid "support" msgstr "tuki" -#, fuzzy msgid "webmaster" -msgstr "kehittäjä & verkkosivujen ylläpitäjä" +msgstr "verkkosivujen ylläpitäjä" msgid "Senior Contributor/QA" msgstr "Vanhempi osallistuja/laadunvarmistus" @@ -11218,7 +11164,7 @@ msgstr "tuki/laadunvarmistus" msgid "XMPP" -msgstr "" +msgstr "XMPP" msgid "original author" msgstr "alkuperäinen tekijä" @@ -12080,7 +12026,7 @@ " Ilman tätä vain ensimmäinen tili otetaan käyttöön).\n" " -v, --version näytä nykyinen versionumero ja poistu\n" -#, fuzzy, c-format +#, c-format msgid "" "%s %s has segfaulted and attempted to dump a core file.\n" "This is a bug in the software and has happened through\n" @@ -12107,11 +12053,6 @@ "pinolistaus muistivedostiedostosta. Jos et tiedä kuinka\n" "pinolistaus (backtrace) haetaan, lue ohjeita osoitteessa\n" "%swiki/GetABacktrace\n" -"\n" -"Jos tarvitset lisäapua, lähetä pikaviesti joko tunnukselle SeanEgn tai " -"LSchiere (AIMissa). Seanin ja Luken yhteystiedot muilla yhteyskäytännöillä " -"ovat osoitteessa\n" -"%swiki/DeveloperPages\n" #. Translators may want to transliterate the name. #. It is not to be translated. @@ -12806,13 +12747,11 @@ msgid "Custom Smiley Manager" msgstr "Omien hymiöiden hallinta" -#, fuzzy msgid "Click to change your buddyicon for this account." -msgstr "Käytä tätä tuttavakuvaketta tälle käyttäjät_ilille:" - -#, fuzzy +msgstr "Napsauta muuttaaksesi tämän käyttäjätilin tuttavakuvaketta." + msgid "Click to change your buddyicon for all accounts." -msgstr "Käytä tätä tuttavakuvaketta tälle käyttäjät_ilille:" +msgstr "Napsauta muuttaaksesi kaikkien käyttäjätilien tuttavakuvaketta." msgid "Waiting for network connection" msgstr "Odotetaan verkkoyhteyttä" @@ -12948,13 +12887,11 @@ msgid "_Invite" msgstr "_Kutsu" -#, fuzzy msgid "_Modify..." -msgstr "_Muokkaa" - -#, fuzzy +msgstr "_Muokkaa..." + msgid "_Add..." -msgstr "_Lisää" +msgstr "_Lisää..." msgid "_Open Mail" msgstr "_Avaa sähköposti" @@ -12977,12 +12914,11 @@ msgid "none" msgstr "ei mitään" -#, fuzzy msgid "Small" -msgstr "_Pienempi" +msgstr "Pienet" msgid "Smaller versions of the default smilies" -msgstr "" +msgstr "Pienemmät versiot oletushymiöistä" msgid "Response Probability:" msgstr "Vastaustodennäköisyys:" @@ -13446,9 +13382,8 @@ msgid "Set window manager \"_URGENT\" hint" msgstr "Aseta ikkunointiohjelman \"_URGENT\"(kiireellinen)-lippu" -#, fuzzy msgid "_Flash window" -msgstr "_Ryhmäkeskusteluikkunoille" +msgstr "_Välkäytä ikkunaa" #. Raise window method button msgid "R_aise conversation window" @@ -13633,18 +13568,16 @@ #, c-format msgid "You can upgrade to %s %s today." -msgstr "" +msgstr "Voit päivittää versioon %s %s." msgid "New Version Available" msgstr "Uusi versio saatavilla" -#, fuzzy msgid "Later" -msgstr "Päiväys" - -#, fuzzy +msgstr "Myöhemmin" + msgid "Download Now" -msgstr "Lataa %s: %s" +msgstr "Lataa nyt" #. *< type #. *< ui_requirement @@ -13964,6 +13897,47 @@ "Tätä liitännäistä voidaan käyttää XMPP-palvelimien tai -asiakasohjelmien " "virheenjäljitykseen." +#~ msgid "Connection to server lost (no data received within %d second)" +#~ msgid_plural "" +#~ "Connection to server lost (no data received within %d seconds)" +#~ msgstr[0] "" +#~ "Yhteys palvelimelle katkesi (dataa ei vastaanotettu %d sekunnissa)" +#~ msgstr[1] "" +#~ "Yhteys palvelimelle katkesi (dataa ei vastaanotettu %d sekunnissa)" + +#, fuzzy +#~ msgid "Add buddy Q&A" +#~ msgstr "Lisää tuttava" + +#, fuzzy +#~ msgid "Can not decrypt get server reply" +#~ msgstr "Kirjautumisvastauksen salausta ei voi purkaa" + +#~ msgid "Keep alive error" +#~ msgstr "Jatkuvan yhteydenpidon virhe" + +#~ msgid "" +#~ "Lost connection with server:\n" +#~ "%d, %s" +#~ msgstr "" +#~ "Yhteys palvelimeen katkesi:\n" +#~ "%d, %s" + +#, fuzzy +#~ msgid "Connecting server ..." +#~ msgstr "Yhdistä palvelimeen" + +# c-format +#~ msgid "Failed to send IM." +#~ msgstr "Viestin lähettäminen epäonnistui." + +#, fuzzy +#~ msgid "Not a member of room \"%s\"\n" +#~ msgstr "Sinä et ole ryhmän \"%s\" jäsen\n" + +#~ msgid "User information for %s unavailable" +#~ msgstr "%s:n käyttäjätiedot eivät ole saatavilla" + #~ msgid "A group with the name already exists." #~ msgstr "Valitun niminen ryhmä on jo olemassa" @@ -14059,15 +14033,6 @@ #~ msgid "Change Qun information" #~ msgstr "Kanavatiedot" -#~ msgid "" -#~ "%s\n" -#~ "\n" -#~ "%s" -#~ msgstr "" -#~ "%s\n" -#~ "\n" -#~ "%s" - #~ msgid "System Message" #~ msgstr "Järjestelmäviesti" @@ -15919,9 +15884,6 @@ #~ msgid "Away title: " #~ msgstr "Poissaolon otsikko: " -#~ msgid "Buddy List Error" -#~ msgstr "Tuttavalistan virhe" - #~ msgid "" #~ "Usage: %s command [OPTIONS] [URI]\n" #~ "\n"
--- a/po/fr.po Tue Dec 16 19:17:44 2008 +0000 +++ b/po/fr.po Tue Dec 16 19:18:41 2008 +0000 @@ -21,8 +21,8 @@ msgstr "" "Project-Id-Version: Pidgin\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-12-01 15:52-0800\n" -"PO-Revision-Date: 2008-08-26 14:30+0200\n" +"POT-Creation-Date: 2008-12-16 10:10+0100\n" +"PO-Revision-Date: 2008-12-16 10:09+0100\n" "Last-Translator: Éric Boumaour <zongo_fr@users.sourceforge.net>\n" "Language-Team: fr <fr@li.org>\n" "MIME-Version: 1.0\n" @@ -85,7 +85,6 @@ msgid "Remember password" msgstr "Mémoriser le mot de passe" -#, fuzzy msgid "There are no protocol plugins installed." msgstr "Aucun plugin de protocole n'est installé." @@ -1526,10 +1525,10 @@ msgstr "Pas de groupement" msgid "Nested Subgroup" -msgstr "" +msgstr "Sous-groupe emboité" msgid "Nested Grouping (experimental)" -msgstr "" +msgstr "Emboitage de groupes (expérimental)" msgid "Provides alternate buddylist grouping options." msgstr "Fournit des options supplémentaires pour le regroupement des contacts." @@ -1848,7 +1847,7 @@ #, c-format msgid "Resolver process exited without answering our request" -msgstr "" +msgstr "Le processus de résolution s'est arrêté sans répondre." #, c-format msgid "Thread creation failure: %s" @@ -4578,7 +4577,6 @@ #. this should probably be part of global smiley theme settings later on, #. shared with MSN -#, fuzzy msgid "Show Custom Smileys" msgstr "Afficher les frimousses personnalisées" @@ -4708,6 +4706,19 @@ msgid "Unable to retrieve MSN Address Book" msgstr "Impossible de récupérer le carnet d'adresses MSN" +#. only notify the user about problems adding to the friends list +#. * maybe we should do something else for other lists, but it probably +#. * won't cause too many problems if we just ignore it +#, c-format +msgid "Unable to add \"%s\"." +msgstr "Impossible d'ajouter « %s »" + +msgid "Buddy Add error" +msgstr "Erreur à l'ajout de l'utilisateur" + +msgid "The username specified does not exist." +msgstr "Le nom d'utilisateur fourni est non valide." + #, c-format msgid "Buddy list synchronization issue in %s (%s)" msgstr "Problème de synchronisation de la liste de contact avec %s (%s)" @@ -4936,9 +4947,9 @@ msgid "Passport account not yet verified" msgstr "Le compte Passeport n'est pas encore validé" -#, fuzzy, c-format +#, c-format msgid "Passport account suspended" -msgstr "Le compte Passeport n'est pas encore validé" +msgstr "Le compte Passeport est suspendu" #, c-format msgid "Bad ticket" @@ -4952,13 +4963,11 @@ msgid "MSN Error: %s\n" msgstr "Erreur MSN : %s\n" -#, fuzzy msgid "Other Contacts" -msgstr "Méthodes de contact préférées" - -#, fuzzy +msgstr "AUtres contacts" + msgid "Non-IM Contacts" -msgstr "Supprimer un contact" +msgstr "Contacts non instantanés" msgid "Nudge" msgstr "Nudge" @@ -5031,6 +5040,12 @@ msgid "Page" msgstr "Envoyer" +msgid "Playing a game" +msgstr "Joue" + +msgid "Working" +msgstr "Travaille" + msgid "Has you" msgstr "Vous êtes dans sa liste" @@ -5067,6 +5082,12 @@ msgid "Album" msgstr "Album" +msgid "Game Title" +msgstr "Nom du jeu" + +msgid "Office Title" +msgstr "Nom du travail" + msgid "Set Friendly Name..." msgstr "Changer l'alias..." @@ -5259,8 +5280,8 @@ "Impossible de récupérer des informations sur le profil de l'utilisateur. Il " "est possible que cet utilisateur n'existe pas." -msgid "Profile URL" -msgstr "Lien du profil" +msgid "View web profile" +msgstr "Voir le profil web" #. *< type #. *< ui_requirement @@ -5315,9 +5336,8 @@ msgid "Unable to add user" msgstr "Impossible d'ajouter un utilisateur" -#, fuzzy msgid "The following users are missing from your addressbook" -msgstr "Voici les résultats de votre recherche." +msgstr "Les utilisateurs suivants manquent dans votre carnet d'adresse" #, c-format msgid "Unable to add user on %s (%s)" @@ -5509,20 +5529,11 @@ msgid "%s has removed you from his or her buddy list." msgstr "L'utilisateur %s vous a supprimé de sa liste de contacts." -#, fuzzy msgid "Delete Buddy from Address Book?" -msgstr "Ajouter au carnet d'adresses" - -#, fuzzy +msgstr "Supprimer le contact du carnet d'adresses ?" + msgid "Do you want to delete this buddy from your address book as well?" -msgstr "Voulez-vous l'ajouter à votre liste de contacts ?" - -#. only notify the user about problems adding to the friends list -#. * maybe we should do something else for other lists, but it probably -#. * won't cause too many problems if we just ignore it -#, c-format -msgid "Unable to add \"%s\"." -msgstr "Impossible d'ajouter « %s »" +msgstr "Voulez-vous aussi supprimer ce contact de votre carnet d'adresses ?" msgid "The username specified is invalid." msgstr "Le nom d'utilisateur fourni est non valide." @@ -5530,6 +5541,9 @@ msgid "This Hotmail account may not be active." msgstr "Ce compte Hotmail ne semble pas être actif." +msgid "Profile URL" +msgstr "Lien du profil" + #. *< type #. *< ui_requirement #. *< flags @@ -5543,18 +5557,12 @@ msgid "MSN Protocol Plugin" msgstr "Plugin pour le protocole MSN" -msgid "Missing Cipher" -msgstr "Chiffre manquant" - -msgid "The RC4 cipher could not be found" -msgstr "Le chiffre RC4 n'a pas été trouvé." - -msgid "" -"Upgrade to a libpurple with RC4 support (>= 2.0.1). MySpaceIM plugin will " -"not be loaded." -msgstr "" -"Le plugin MySpaceIM ne sera pas chargé. Mettez à jour libpurple avec le " -"support de RC4 (>= 2.0.1)." +#, c-format +msgid "No such user: %s" +msgstr "Utilisateur inconnu : %s" + +msgid "User lookup" +msgstr "Recherche d'utilisateur" msgid "Reading challenge" msgstr "Lecture du défi" @@ -5565,12 +5573,19 @@ msgid "Logging in" msgstr "Connexion" -#, c-format -msgid "Connection to server lost (no data received within %d second)" -msgid_plural "Connection to server lost (no data received within %d seconds)" -msgstr[0] "Connexion avec le serveur perdue (aucune donnée depuis %d seconde)." -msgstr[1] "" -"Connexion avec le serveur perdue (aucune donnée depuis %d secondes)." +msgid "MySpaceIM - No Username Set" +msgstr "MySpaceIM - Aucun nom d'utilisateur fourni" + +msgid "You appear to have no MySpace username." +msgstr "Il semblerait que vous n'aillez pas de nom d'utilisateur MySpace." + +msgid "Would you like to set one now? (Note: THIS CANNOT BE CHANGED!)" +msgstr "" +"Voulez-vous en fournir un maintenant ? (Attention : IL NE POURRA PLUS ÊTRE " +"CHANGÉ !)" + +msgid "Lost connection with server" +msgstr "Connexion perdue avec le serveur" #. Can't write _()'d strings in array initializers. Workaround. msgid "New mail messages" @@ -5591,16 +5606,25 @@ msgid "MySpace" msgstr "MySpace" -msgid "MySpaceIM - No Username Set" -msgstr "MySpaceIM - Aucun nom d'utilisateur fourni" - -msgid "You appear to have no MySpace username." -msgstr "Il semblerait que vous n'aillez pas de nom d'utilisateur MySpace." - -msgid "Would you like to set one now? (Note: THIS CANNOT BE CHANGED!)" -msgstr "" -"Voulez-vous en fournir un maintenant ? (Attention : IL NE POURRA PLUS ÊTRE " -"CHANGÉ !)" +msgid "IM Friends" +msgstr "Amis de messagerie" + +#, c-format +msgid "" +"%d buddy was added or updated from the server (including buddies already on " +"the server-side list)" +msgid_plural "" +"%d buddies were added or updated from the server (including buddies already " +"on the server-side list)" +msgstr[0] "" +"%d contact a été ajouté ou mis à jour depuis le serveur (y compris les " +"contacts déjà sur la liste du serveur)." +msgstr[1] "" +"%d contacts ont été ajoutés ou mis à jour depuis le serveur (y compris les " +"contacts déjà sur la liste du serveur)." + +msgid "Add contacts from server" +msgstr "Ajouter les contacts du serveur" #. The session is now set up, ready to be connected. This emits the #. * signedOn signal, so clients can now do anything with msimprpl, and @@ -5627,31 +5651,6 @@ msgid "MySpaceIM Error" msgstr "Erreur MySpaceIM" -msgid "Failed to add buddy" -msgstr "Échec lors de l'ajout du contact." - -msgid "'addbuddy' command failed." -msgstr "Échec de la commande « addbuddy »." - -msgid "persist command failed" -msgstr "Échec de la commande « persist »." - -#, c-format -msgid "No such user: %s" -msgstr "Utilisateur inconnu : %s" - -msgid "User lookup" -msgstr "Recherche d'utilisateur" - -msgid "Failed to remove buddy" -msgstr "Échec lors de la suppression du contact." - -msgid "'delbuddy' command failed" -msgstr "Échec de la commande « delbuddy »." - -msgid "blocklist command failed" -msgstr "Échec de la commande « blocklist »." - msgid "Invalid input condition" msgstr "Condition de saisie non valide." @@ -5665,25 +5664,36 @@ msgid "Couldn't connect to host: %s (%d)" msgstr "Impossible de se connecter à l'hôte : %s (%d)" -msgid "IM Friends" -msgstr "Amis de messagerie" - -#, c-format -msgid "" -"%d buddy was added or updated from the server (including buddies already on " -"the server-side list)" -msgid_plural "" -"%d buddies were added or updated from the server (including buddies already " -"on the server-side list)" -msgstr[0] "" -"%d contact a été ajouté ou mis à jour depuis le serveur (y compris les " -"contacts déjà sur la liste du serveur)." -msgstr[1] "" -"%d contacts ont été ajoutés ou mis à jour depuis le serveur (y compris les " -"contacts déjà sur la liste du serveur)." - -msgid "Add contacts from server" -msgstr "Ajouter les contacts du serveur" +msgid "Failed to add buddy" +msgstr "Échec lors de l'ajout du contact." + +msgid "'addbuddy' command failed." +msgstr "Échec de la commande « addbuddy »." + +msgid "persist command failed" +msgstr "Échec de la commande « persist »." + +msgid "Failed to remove buddy" +msgstr "Échec lors de la suppression du contact." + +msgid "'delbuddy' command failed" +msgstr "Échec de la commande « delbuddy »." + +msgid "blocklist command failed" +msgstr "Échec de la commande « blocklist »." + +msgid "Missing Cipher" +msgstr "Chiffre manquant" + +msgid "The RC4 cipher could not be found" +msgstr "Le chiffre RC4 n'a pas été trouvé." + +msgid "" +"Upgrade to a libpurple with RC4 support (>= 2.0.1). MySpaceIM plugin will " +"not be loaded." +msgstr "" +"Le plugin MySpaceIM ne sera pas chargé. Mettez à jour libpurple avec le " +"support de RC4 (>= 2.0.1)." msgid "Add friends from MySpace.com" msgstr "Ajouter les amis de MySpace.com" @@ -5725,9 +5735,6 @@ msgid "User" msgstr "Utilisateur" -msgid "Profile" -msgstr "Informations" - msgid "Headline" msgstr "En-tête" @@ -5740,16 +5747,6 @@ msgid "Client Version" msgstr "Version du client" -#. Protocol won't log in now without a username set.. Disconnect -msgid "No username set" -msgstr "Aucun nom d'utilisateur fourni" - -msgid "MySpaceIM - Please Set a Username" -msgstr "MySpaceIM - Veuillez fournir un nom d'utilisateur" - -msgid "Please enter a username to check its availability:" -msgstr "Saisissez un nom d'utilisateur pour vérifier sa disponibilité :" - msgid "MySpaceIM - Username Available" msgstr "MySpaceIM - Nom d'utilisateur disponible" @@ -5759,12 +5756,22 @@ msgid "ONCE SET, THIS CANNOT BE CHANGED!" msgstr "Une fois choisi, IL NE POURRA PAS ÊTRE CHANGÉ !" +msgid "MySpaceIM - Please Set a Username" +msgstr "MySpaceIM - Veuillez fournir un nom d'utilisateur" + msgid "This username is unavailable." msgstr "Ce nom d'utilisateur n'est pas disponible." msgid "Please try another username:" msgstr "Veuillez en essayer un autre :" +#. Protocol won't log in now without a username set.. Disconnect +msgid "No username set" +msgstr "Aucun nom d'utilisateur fourni" + +msgid "Please enter a username to check its availability:" +msgstr "Saisissez un nom d'utilisateur pour vérifier sa disponibilité :" + #. TODO: icons for each zap #. Lots of comments for translators: #. Zap means "to strike suddenly and forcefully as if with a @@ -6212,7 +6219,7 @@ msgstr "Plugin pour le protocole AIM" msgid "ICQ UIN..." -msgstr "" +msgstr "UIN ICQ..." #. *< type #. *< ui_requirement @@ -6545,8 +6552,8 @@ "You may be disconnected shortly. You may want to use TOC until this is " "fixed. Check %s for updates." msgstr "" -"Vous risquez être déconnecté sous peu. Essayer d'utiliser TOC en attendant. " -"Regardez %s pour plus d'informations." +"Vous risquez d'être déconnecté sous peu. Veuillez essayer d'utiliser TOC " +"entre-temps si cela arrive. Visitez %s pour plus d'informations." msgid "Unable to get a valid AIM login hash." msgstr "Impossible de récupérer un code de connexion AIM valide." @@ -6723,6 +6730,9 @@ msgid "Member Since" msgstr "Inscrit depuis" +msgid "Profile" +msgstr "Informations" + msgid "Your AIM connection may be lost." msgstr "Votre connexion AIM risque d'être coupée" @@ -6914,11 +6924,9 @@ "soit commencer par une lettre et contenir uniquement des lettres, des " "chiffres et des espaces, soit contenir uniquement des chiffres." -#, fuzzy msgid "Unable to Add" msgstr "Impossible d'ajouter" -#, fuzzy msgid "Unable to Retrieve Buddy List" msgstr "Impossible de récupérer la liste de contacts" @@ -7219,16 +7227,14 @@ msgid "Other" msgstr "Autre" -#, fuzzy msgid "Visible" -msgstr "Invisible" - -msgid "Firend Only" -msgstr "" - -#, fuzzy +msgstr "Visible" + +msgid "Friend Only" +msgstr "Amis seulement" + msgid "Private" -msgstr "Filtres" +msgstr "Privé" msgid "QQ Number" msgstr "Numéro QQ" @@ -7245,9 +7251,8 @@ msgid "Phone Number" msgstr "Téléphone fixe" -#, fuzzy msgid "Authorize adding" -msgstr "Autoriser le contact ?" +msgstr "Autoriser l'ajout" msgid "Cellphone Number" msgstr "Téléphone portable" @@ -7255,132 +7260,108 @@ msgid "Personal Introduction" msgstr "Informations personnelles" -#, fuzzy msgid "City/Area" -msgstr "Localité" - -#, fuzzy +msgstr "Ville/Localité" + msgid "Publish Mobile" -msgstr "Téléphone portable personnel" - -#, fuzzy +msgstr "Publier le téléphone portable" + msgid "Publish Contact" -msgstr "Donner un alias à un contact" +msgstr "Publier les informations de contact" msgid "College" msgstr "Éducation" -#, fuzzy msgid "Horoscope" msgstr "Signe du zodiaque" -#, fuzzy msgid "Zodiac" msgstr "Signe du zodiaque chinois" -#, fuzzy msgid "Blood" -msgstr "Bloqué" - -#, fuzzy +msgstr "Groupe sanguin" + msgid "True" -msgstr "Taureau" - -#, fuzzy +msgstr "Vrai" + msgid "False" -msgstr "Échec" - -#, fuzzy +msgstr "Faux" + msgid "Modify Contact" -msgstr "Modification du compte" - -#, fuzzy +msgstr "Modifier les infos de contact" + msgid "Modify Address" -msgstr "Adresse personnelle" - -#, fuzzy +msgstr "Modifier l'adresse" + msgid "Modify Extended Information" -msgstr "Modifier mes informations" - -#, fuzzy +msgstr "Modifier mes infos étendues" + msgid "Modify Information" msgstr "Modifier mes informations" -#, fuzzy msgid "Update" -msgstr "Dernière mise à jour" - -#, fuzzy +msgstr "Mettre à jour" + msgid "Could not change buddy information." -msgstr "Veuillez saisir les informations du contact." - -#, c-format -msgid "%d needs Q&A" -msgstr "" - -#, fuzzy -msgid "Add buddy Q&A" -msgstr "Ajouter le contact" - -#, fuzzy -msgid "Input answer here" -msgstr "Saisissez votre demande" +msgstr "Impossible de changer les informations du contact." + +#, c-format +msgid "%u requires verification" +msgstr "%u demande une vérification" + +msgid "Add buddy question" +msgstr "Ajouter une question pour les nouveaux contacts" + +msgid "Enter answer here" +msgstr "Saisissez la réponse ici" msgid "Send" msgstr "Envoyer" -#, fuzzy msgid "Invalid answer." -msgstr "Nom d'utilisateur non valide." +msgstr "Réponse non valide." msgid "Authorization denied message:" msgstr "Message de refus d'autorisation :" -#, fuzzy -msgid "Sorry, You are not my style." -msgstr "Désolé, tu n'es pas mon genre..." - -#, fuzzy, c-format -msgid "%d needs authentication" -msgstr "L'utilisateur %d demande une authentification." - -#, fuzzy +msgid "Sorry, you're not my style." +msgstr "Désolé, tu n'es pas mon genre." + +#, c-format +msgid "%u needs authorization" +msgstr "%u demande une autorisation" + msgid "Add buddy authorize" -msgstr "Ajouter cet utilisateur à la liste de contacts ?" - -msgid "Input request here" +msgstr "Ajouter une autorisation de contact" + +msgid "Enter request here" msgstr "Saisissez votre demande" msgid "Would you be my friend?" msgstr "Veux-tu être mon ami ?" -#, fuzzy msgid "QQ Buddy" -msgstr "Contact" - -#, fuzzy +msgstr "Contact QQ" + msgid "Add buddy" -msgstr "Ajouter le contact" - -#, fuzzy +msgstr "Ajouter un contact" + msgid "Invalid QQ Number" -msgstr "QQ Face non valide" - -#, fuzzy +msgstr "Numéro QQ non valide" + msgid "Failed sending authorize" -msgstr "Autorise moi, s'il te plaît !" - -#, fuzzy, c-format -msgid "Failed removing buddy %d" -msgstr "Échec lors de la suppression du contact." - -#, fuzzy, c-format +msgstr "Échec à l'envoi de l'autorisation" + +#, c-format +msgid "Failed removing buddy %u" +msgstr "Échec lors de la suppression du contact %u" + +#, c-format msgid "Failed removing me from %d's buddy list" -msgstr "L'utilisateur %s vous a supprimé de sa liste de contacts." - -#, fuzzy +msgstr "Échec lors de ma suppression de la liste de %d" + msgid "No reason given" -msgstr "Pas de raison" +msgstr "Pas de raison donnée" #. only need to get value #, c-format @@ -7390,9 +7371,9 @@ msgid "Would you like to add him?" msgstr "Voulez-vous l'ajouter ?" -#, fuzzy, c-format +#, c-format msgid "Rejected by %s" -msgstr "Refuser" +msgstr "Refusé par %s" #, c-format msgid "Message: %s" @@ -7407,81 +7388,73 @@ msgid "QQ Qun" msgstr "QQ Qun" -#, fuzzy msgid "Please enter Qun number" -msgstr "Saisissez le nouveau nom pour %s" - -#, fuzzy +msgstr "Saisissez le numéro Qun" + msgid "You can only search for permanent Qun\n" -msgstr "Vous ne pouvez cherche que les groupes QQ permanents.\n" - -#, fuzzy +msgstr "Vous ne pouvez chercher que les Qun permanents.\n" + +msgid "(Invalid UTF-8 string)" +msgstr "(Chaine de caractères UTF-8 non valide)" + msgid "Not member" -msgstr "Je n'en suis pas membre." +msgstr "Non membre" msgid "Member" msgstr "Membre" -#, fuzzy msgid "Requesting" -msgstr "Boite de message pour requête" - -#, fuzzy +msgstr "Demande en cours" + msgid "Admin" -msgstr "Adium" - -#, fuzzy +msgstr "Admin" + msgid "Notice" -msgstr "Commentaire" - -#, fuzzy +msgstr "Envoi d'infos" + msgid "Detail" -msgstr "Par défaut" +msgstr "Détail" msgid "Creator" msgstr "Créateur" -#, fuzzy msgid "About me" -msgstr "À propos de %s" - -#, fuzzy +msgstr "À mon propos" + msgid "Category" -msgstr "Erreur de discussion" - -#, fuzzy +msgstr "Catégorie" + msgid "The Qun does not allow others to join" -msgstr "Ce groupe est fermé aux inscriptions." - -#, fuzzy +msgstr "Ce Qun est fermé aux inscriptions" + msgid "Join QQ Qun" -msgstr "Rejoindre une discussion" - -#, c-format -msgid "Successfully joined Qun %s (%d)" -msgstr "" - -#, fuzzy +msgstr "Rejoindre un Qun QQ" + +msgid "Input request here" +msgstr "Saisissez votre demande" + +#, c-format +msgid "Successfully joined Qun %s (%u)" +msgstr "Entrée réussie dans le Qun %s (%u)" + msgid "Successfully joined Qun" -msgstr "Vous avez modifié la liste des membres du Qun." - -#, c-format -msgid "Qun %d denied to join" -msgstr "" +msgstr "Entrée réussie dans le Qun" + +#, c-format +msgid "Qun %u denied from joining" +msgstr "L'entrée dans le Qun %u a été refusée" msgid "QQ Qun Operation" msgstr "Opération QQ Qun" -#, fuzzy msgid "Failed:" -msgstr "Échec" - -msgid "Join Qun, Unknow Reply" -msgstr "" - -#, fuzzy +msgstr "Échec :" + +msgid "Join Qun, Unknown Reply" +msgstr "Joindre un Qun, réponse inconnue" + msgid "Quit Qun" -msgstr "QQ Qun" +msgstr "Parir du Qun" msgid "" "Note, if you are the creator, \n" @@ -7490,51 +7463,47 @@ "Note : si vous en êtes le créateur, \n" "cette opération peut supprimer ce Qun." -#, fuzzy -msgid "Sorry, you are not our style ..." -msgstr "Désolé, tu n'es pas mon genre..." - -#, fuzzy -msgid "Successfully changed Qun member" +msgid "Sorry, you are not our style" +msgstr "Désolés, tu n'es pas notre genre." + +msgid "Successfully changed Qun members" msgstr "Vous avez modifié la liste des membres du Qun." -#, fuzzy msgid "Successfully changed Qun information" msgstr "Vous avez modifié les informations du Qun." msgid "You have successfully created a Qun" msgstr "Vous avez créé un Qun." -#, fuzzy -msgid "Would you like to set detailed information now?" -msgstr "Voulez-vous modifier les paramètres du Qun maintenant ?" +msgid "Would you like to set up detailed information now?" +msgstr "Voulez-vous modifier les infos détaillées maintenant ?" msgid "Setup" msgstr "Options" -#, fuzzy, c-format -msgid "%d requested to join Qun %d for %s" -msgstr "L'utilisateur %d demande à rejoindre le groupe %d." - -#, fuzzy, c-format -msgid "%d request to join Qun %d" -msgstr "L'utilisateur %d demande à rejoindre le groupe %d." - -#, fuzzy, c-format -msgid "Failed to join Qun %d, operated by admin %d" -msgstr "Impossible de joindre le contact dans la discussion" - -#, c-format -msgid "<b>Joining Qun %d is approved by admin %d for %s</b>" -msgstr "" - -#, fuzzy, c-format -msgid "<b>Removed buddy %d.</b>" -msgstr "Supprimer un contact" - -#, c-format -msgid "<b>New buddy %d joined.</b>" -msgstr "" +#, c-format +msgid "%u requested to join Qun %u for %s" +msgstr "L'utilisateur %u demande à rejoindre le Qun %u pour %s" + +#, c-format +msgid "%u request to join Qun %u" +msgstr "L'utilisateur %u demande à rejoindre le Qun %u" + +#, c-format +msgid "Failed to join Qun %u, operated by admin %u" +msgstr "Échec pour rejoindre le Qun %u, administré par %u" + +#, c-format +msgid "<b>Joining Qun %u is approved by admin %u for %s</b>" +msgstr "<b>L'entrée dans le Qun %u a été approuvée par l'admin %u pour %s</b>" + +#, c-format +msgid "<b>Removed buddy %u.</b>" +msgstr "<b>Contact supprimé %u.</b>" + +#, c-format +msgid "<b>New buddy %u joined.</b>" +msgstr "<b>Nouveau contact %u entré.</b>" #, c-format msgid "Unknown-%d" @@ -7558,9 +7527,8 @@ msgid " Video" msgstr " Vidéo" -#, fuzzy msgid " Zone" -msgstr "Aucun" +msgstr " Zone" msgid "Flag" msgstr "Drapeau" @@ -7571,110 +7539,104 @@ msgid "Invalid name" msgstr "Nom d'utilisateur non valide" -#, fuzzy msgid "Select icon..." -msgstr "Choisir un dossier..." - -#, fuzzy, c-format +msgstr "Choisir une icône..." + +#, c-format msgid "<b>Login time</b>: %d-%d-%d, %d:%d:%d<br>\n" -msgstr "<b>Heure de connexion :</b> %s<br>\n" - -#, fuzzy, c-format +msgstr "<b>Heure de connexion :</b> %d-%d-%d, %d:%d:%d<br>\n" + +#, c-format msgid "<b>Total Online Buddies</b>: %d<br>\n" -msgstr "<b>Connectés :</b> %d<br>\n" - -#, fuzzy, c-format +msgstr "<b>Contacts connectés :</b> %d<br>\n" + +#, c-format msgid "<b>Last Refresh</b>: %d-%d-%d, %d:%d:%d<br>\n" -msgstr "<b>Dernière mise à jour :</b> %s<br>\n" - -#, fuzzy, c-format +msgstr "<b>Dernière mise à jour :</b> %d-%d-%d, %d:%d:%d<br>\n" + +#, c-format msgid "<b>Server</b>: %s<br>\n" -msgstr "<b>Serveur :</b> %s: %d<br>\n" - -#, fuzzy, c-format +msgstr "<b>Serveur :</b> %s<br>\n" + +#, c-format msgid "<b>Client Tag</b>: %s<br>\n" -msgstr "<b>Heure de connexion :</b> %s<br>\n" +msgstr "<b>Étiquette du client :</b> %s<br>\n" #, c-format msgid "<b>Connection Mode</b>: %s<br>\n" msgstr "<b>Type de connexion :</b> %s<br>\n" -#, fuzzy, c-format +#, c-format msgid "<b>My Internet IP</b>: %s:%d<br>\n" -msgstr "<b>Type de connexion :</b> %s<br>\n" - -#, fuzzy, c-format +msgstr "<b>Adresse internet IP :</b> %s:%d<br>\n" + +#, c-format msgid "<b>Sent</b>: %lu<br>\n" -msgstr "<b>Serveur :</b> %s: %d<br>\n" - -#, fuzzy, c-format +msgstr "<b>Envoyés :</b> %lu<br>\n" + +#, c-format msgid "<b>Resend</b>: %lu<br>\n" -msgstr "<b>Dernière mise à jour :</b> %s<br>\n" - -#, fuzzy, c-format +msgstr "<b>Ré-envoyés :</b> %lu<br>\n" + +#, c-format msgid "<b>Lost</b>: %lu<br>\n" -msgstr "<b>Dernière mise à jour :</b> %s<br>\n" - -#, fuzzy, c-format +msgstr "<b>Perdus :</b> %lu<br>\n" + +#, c-format msgid "<b>Received</b>: %lu<br>\n" -msgstr "<b>Serveur :</b> %s: %d<br>\n" - -#, fuzzy, c-format +msgstr "<b>Reçus :</b> %lu<br>\n" + +#, c-format msgid "<b>Received Duplicate</b>: %lu<br>\n" -msgstr "<b>Adresse IP publiée :</b> %s<br>\n" - -#, fuzzy, c-format +msgstr "<b>Doubles reçus :</b> %lu<br>\n" + +#, c-format msgid "<b>Time</b>: %d-%d-%d, %d:%d:%d<br>\n" -msgstr "<b>Heure de connexion :</b> %s<br>\n" - -#, fuzzy, c-format +msgstr "<b>Heure :</b> %d-%d-%d, %d:%d:%d<br>\n" + +#, c-format msgid "<b>IP</b>: %s<br>\n" -msgstr "<b>Serveur :</b> %s: %d<br>\n" +msgstr "<b>IP :</b> %s<br>\n" msgid "Login Information" msgstr "Informations de connexion" msgid "<p><b>Original Author</b>:<br>\n" -msgstr "" +msgstr "<p><b>Auteur original :</b><br>\n" msgid "<p><b>Code Contributors</b>:<br>\n" -msgstr "" - -#, fuzzy +msgstr "<p><b>Contributeurs au code :</b><br>\n" + msgid "<p><b>Lovely Patch Writers</b>:<br>\n" -msgstr "<b>Dernière mise à jour :</b> %s<br>\n" - -#, fuzzy +msgstr "<p><b>Charmants contributeurs de patchs :</b><br>\n" + msgid "<p><b>Acknowledgement</b>:<br>\n" -msgstr "<b>Serveur :</b> %s: %d<br>\n" +msgstr "<p><b>Remerciements :</b><br>\n" msgid "<p><i>And, all the boys in the backroom...</i><br>\n" -msgstr "" +msgstr "<p><i>et tous les personnes dans les coulisses...</i><br>\n" msgid "<i>Feel free to join us!</i> :)" -msgstr "" - -#, fuzzy, c-format -msgid "About OpenQ r%s" -msgstr "À propos de %s" - -#, fuzzy +msgstr "<i>N'hésitez pas à mous rejoindre !</i> :)" + +#, c-format +msgid "About OpenQ %s" +msgstr "À propos de OpenQ %s" + msgid "Change Icon" -msgstr "Enregistrer l'icône" +msgstr "Changer l'icône" msgid "Change Password" msgstr "Changer de mot de passe" -#, fuzzy msgid "Account Information" -msgstr "Informations de connexion" +msgstr "Informations du compte" msgid "Update all QQ Quns" -msgstr "" - -#, fuzzy +msgstr "Mettre à jour tous les Quns QQ" + msgid "About OpenQ" -msgstr "À propos de %s" +msgstr "À propos de OpenQ" #. *< type #. *< ui_requirement @@ -7686,59 +7648,45 @@ #. *< version #. * summary #. * description -#, fuzzy msgid "QQ Protocol Plugin" msgstr "Plugin pour le protocole QQ" -#, fuzzy msgid "Auto" -msgstr "Auteur" - -#, fuzzy +msgstr "Auto" + msgid "Select Server" -msgstr "Choisir l'utilisateur" +msgstr "Choisir le serveur" msgid "QQ2005" -msgstr "" +msgstr "QQ2005" msgid "QQ2007" -msgstr "" +msgstr "QQ2007" msgid "QQ2008" -msgstr "" - -#. #endif -#, fuzzy +msgstr "QQ2008" + msgid "Connect by TCP" msgstr "Connexion par TCP" -#, fuzzy msgid "Show server notice" -msgstr "Port du serveur" - -#, fuzzy +msgstr "Afficher les infos du serveur" + msgid "Show server news" -msgstr "Hôte du serveur" - -#, fuzzy +msgstr "Afficher les nouveautés du serveur" + msgid "Keep alive interval (seconds)" -msgstr "Délai(s) de Keep alive" - -#, fuzzy +msgstr "Délai de Keep alive (en secondes)" + msgid "Update interval (seconds)" -msgstr "Délai(s) de mise à jour" - -#, fuzzy -msgid "Can not decrypt server reply" -msgstr "Impossible de déchiffrer la réponse de connexion" - -#, fuzzy -msgid "Can not decrypt get server reply" -msgstr "Impossible de déchiffrer la réponse de connexion" +msgstr "Délai de mise à jour (en secondes)" + +msgid "Cannot decrypt server reply" +msgstr "Impossible de déchiffrer la réponse du serveur" #, c-format msgid "Failed requesting token, 0x%02X" -msgstr "" +msgstr "Échec lors de la demande du token, 0x%02X" #, c-format msgid "Invalid token len, %d" @@ -7746,56 +7694,53 @@ #. extend redirect used in QQ2006 msgid "Redirect_EX is not currently supported" -msgstr "" +msgstr "Redirect_EX n'est pas supportée pour l'instant" #. need activation #. need activation #. need activation -#, fuzzy msgid "Activation required" -msgstr "Erreur d'enregistrement" - -#, fuzzy, c-format -msgid "Unknow reply code when login (0x%02X)" -msgstr "Code du token de réponse non valide : 0x%02X" - -msgid "Keep alive error" -msgstr "Erreur de Keep alive" - -#, fuzzy -msgid "Requesting captcha ..." -msgstr "Attire l'attention de %s..." - -msgid "Checking code of captcha ..." -msgstr "" - -msgid "Failed captcha verify" -msgstr "" - -#, fuzzy +msgstr "Activation nécessaire" + +#, c-format +msgid "Unknown reply code when logging in (0x%02X)" +msgstr "Réponse inconnue à la connexion (0x%02X)" + +msgid "Could not decrypt server reply" +msgstr "Impossible de déchiffrer la réponse du serveur" + +msgid "Requesting captcha" +msgstr "Demande de captcha" + +msgid "Checking captcha" +msgstr "Vérification du code du captcha" + +msgid "Failed captcha verification" +msgstr "Échec à la vérification du captcha" + msgid "Captcha Image" -msgstr "Enregistrer l'image" - -#, fuzzy +msgstr "Image captcha" + msgid "Enter code" -msgstr "Saisissez le mot de passe" - -msgid "QQ Captcha Verifing" -msgstr "" - -#, fuzzy +msgstr "Saisissez le code" + +msgid "QQ Captcha Verification" +msgstr "Vérification du captcha QQ" + msgid "Enter the text from the image" -msgstr "Saisissez le nom du groupe" - -#, c-format -msgid "Unknow reply code when checking password (0x%02X)" -msgstr "" - -#, c-format -msgid "" -"Unknow reply code when login (0x%02X):\n" +msgstr "Saisissez le texte de l'image" + +#, c-format +msgid "Unknown reply when checking password (0x%02X)" +msgstr "Réponse inconnue à la vérification du mot de passe (0x%02X)" + +#, c-format +msgid "" +"Unknown reply code when logging in (0x%02X):\n" "%s" msgstr "" +"Réponse inconnue à la connexion (0x%02X) :\n" +"%s" #. we didn't successfully connect. tdt->toc_fd is valid here msgid "Unable to connect." @@ -7804,14 +7749,6 @@ msgid "Socket error" msgstr "Erreur de socket." -#, c-format -msgid "" -"Lost connection with server:\n" -"%d, %s" -msgstr "" -"Connexion perdue avec le serveur :\n" -"%d, %s" - msgid "Unable to read from socket" msgstr "Impossible de lire le socket." @@ -7821,81 +7758,74 @@ msgid "Connection lost" msgstr "Connexion perdue." -#, fuzzy -msgid "Get server ..." -msgstr "Modifier les informations..." - -#, fuzzy -msgid "Request token" -msgstr "Requête refusée" +msgid "Getting server" +msgstr "Récupération du serveur" + +msgid "Requesting token" +msgstr "Requête d'un token" msgid "Couldn't resolve host" msgstr "Impossible de trouver l'adresse de l'hôte." -#, fuzzy msgid "Invalid server or port" -msgstr "Erreur non valide" - -#, fuzzy -msgid "Connecting server ..." -msgstr "Serveur de connexion" - -#, fuzzy +msgstr "Serveur ou port non valide" + +msgid "Connecting to server" +msgstr "Connexion au serveur" + msgid "QQ Error" -msgstr "Erreur QQid" - -msgid "Failed to send IM." -msgstr "Impossible d'envoyer le message." - -#, fuzzy, c-format +msgstr "Erreur QQ" + +#, c-format msgid "" "Server News:\n" "%s\n" "%s\n" "%s" -msgstr "Relais de serveur ICQ" - -#, fuzzy, c-format +msgstr "" +"Nouveautés du serveur :\n" +"%s\n" +"%s\n" +"%s" + +#, c-format +msgid "%s:%s" +msgstr "%s : %s" + +#, c-format msgid "From %s:" -msgstr "De" - -#, fuzzy, c-format +msgstr "De %s :" + +#, c-format msgid "" "Server notice From %s: \n" "%s" -msgstr "Instructions du serveur : %s" - -msgid "Unknow SERVER CMD" -msgstr "" - -#, fuzzy, c-format +msgstr "" +"Info du serveur de %s : \n" +"%s" + +msgid "Unknown SERVER CMD" +msgstr "Commande SERVER inconnue" + +#, c-format msgid "" "Error reply of %s(0x%02X)\n" -"Room %d, reply 0x%02X" -msgstr "" -"Réponse %s(0x%02X )\n" -"Envoi %s(0x%02X )\n" -"Salon id %d, réponse [0x%02X]: \n" -"%s" - -#, fuzzy +"Room %u, reply 0x%02X" +msgstr "" +"Erreur à la réponse %s(0x%02X)\n" +"Salon %u, réponse 0x%02X" + msgid "QQ Qun Command" -msgstr "Commande" - -#, fuzzy, c-format -msgid "Not a member of room \"%s\"\n" -msgstr "Vous n'êtes pas membre du groupe « %s ».\n" - -msgid "Can not decrypt login reply" +msgstr "Commande Qun QQ" + +msgid "Could not decrypt login reply" msgstr "Impossible de déchiffrer la réponse de connexion" -#, fuzzy -msgid "Unknow LOGIN CMD" -msgstr "Raison inconnue" - -#, fuzzy -msgid "Unknow CLIENT CMD" -msgstr "Raison inconnue" +msgid "Unknown LOGIN CMD" +msgstr "Commande LOGIN inconnue" + +msgid "Unknown CLIENT CMD" +msgstr "Commande CLIENT inconnue" #, c-format msgid "%d has declined the file %s" @@ -9770,9 +9700,8 @@ msgid "doodle: Request user to start a Doodle session" msgstr "doodle : Envoie une demande pour un séance de griffonnage." -#, fuzzy msgid "Yahoo ID..." -msgstr "ID Yahoo!" +msgstr "ID Yahoo..." #. *< type #. *< ui_requirement @@ -9880,13 +9809,8 @@ msgid "Last Update" msgstr "Dernière mise à jour" -#, c-format -msgid "User information for %s unavailable" -msgstr "Les informations sur %s ne sont pas disponibles" - -msgid "" -"Sorry, this profile seems to be in a language or format that is not " -"supported at this time." +msgid "" +"This profile is in a language or format that is not supported at this time." msgstr "" "Désolé, ce profil a l'air d'être dans une langue ou un format non supporté " "pour l'instant." @@ -10329,9 +10253,9 @@ msgid "Unable to connect to %s" msgstr "Impossible de se connecter à %s." -#, fuzzy, c-format +#, c-format msgid "Error reading from %s: response too long (%d bytes limit)" -msgstr "Erreur à la lecture depuis %s : %s" +msgstr "Erreur à la lecture depuis %s : réponse trop longue (%d octets max)" #, c-format msgid "" @@ -10505,7 +10429,7 @@ msgid "Protocol" msgstr "Protocole" -#, fuzzy, c-format +#, c-format msgid "" "<span size='larger' weight='bold'>Welcome to %s!</span>\n" "\n" @@ -10517,7 +10441,7 @@ "You can come back to this window to add, edit, or remove accounts from " "<b>Accounts->Manage Accounts</b> in the Buddy List window" msgstr "" -"<span weight='bold' size='larger'>Bienvenue dans %s !</span>\n" +"<span size='larger' weight='bold'>Bienvenue dans %s !</span>\n" "\n" "Vous n'avez aucun compte configuré. Pour vous connecter avec %s, cliquez le " "bouton <b>Ajouter</b> ci-dessous et configurez votre premier compte. Si vous " @@ -10525,7 +10449,7 @@ "b> autant de fois que nécessaire.\n" "\n" "Pour retrouver cette fenêtre afin d'ajouter, modifier ou supprimer des " -"comptes, choisissez <b>Comptes->Gérer les comptes</b> dans le menu de la " +"comptes, choisissez <b>Comptes->Gérer les comptes</b> dans la fenêtre de la " "liste de contacts." #, c-format @@ -10951,9 +10875,8 @@ msgid "Auto_join when account becomes online." msgstr "Rejoindre _automatiquement quand le compte est activé." -#, fuzzy msgid "_Remain in chat after window is closed." -msgstr "_Cacher le salon de discussions quand la fenêtre est fermée." +msgstr "_Rester dans le salon de discussions quand la fenêtre est fermée." msgid "Please enter the name of the group to be added." msgstr "Saisissez le nom du groupe à ajouter" @@ -11329,11 +11252,10 @@ msgstr "Erreur critique" msgid "bug master" -msgstr "" - -#, fuzzy +msgstr "maitre des bogues" + msgid "artist" -msgstr "Artiste" +msgstr "artiste" #. feel free to not translate this msgid "Ka-Hing Cheung" @@ -11342,9 +11264,8 @@ msgid "support" msgstr "support" -#, fuzzy msgid "webmaster" -msgstr "codeur et webmestre" +msgstr "webmestre" msgid "Senior Contributor/QA" msgstr "contribuant senior/QA" @@ -11366,7 +11287,7 @@ msgstr "support/QA" msgid "XMPP" -msgstr "" +msgstr "XMPP" msgid "original author" msgstr "auteur original" @@ -11443,9 +11364,8 @@ msgid "French" msgstr "Français" -#, fuzzy msgid "Irish" -msgstr "Kurde" +msgstr "Irlandais" msgid "Galician" msgstr "Galicien" @@ -11864,14 +11784,12 @@ msgid "Color to draw hyperlinks." msgstr "Couleur pour afficher les liens hypertextes" -#, fuzzy msgid "Hyperlink visited color" -msgstr "Couleur des liens hypertextes" - -#, fuzzy +msgstr "Couleur des liens visités" + msgid "Color to draw hyperlinks after it has been visited (or activated)." msgstr "" -"Couleur pour afficher les liens hypertextes quand la souris les survole." +"Couleur pour afficher les liens hypertextes une fois visités (ou activés)." msgid "Hyperlink prelight color" msgstr "Couleur de surlignage des liens hypertextes" @@ -12246,7 +12164,7 @@ "activé)\n" " -v, --version affiche le numéro de la version actuelle\n" -#, fuzzy, c-format +#, c-format msgid "" "%s %s has segfaulted and attempted to dump a core file.\n" "This is a bug in the software and has happened through\n" @@ -12274,12 +12192,6 @@ "core. Si vous ne savez pas comment procéder, veuillez suivre\n" "les indications sur\n" "%swiki/GetABacktrace\n" -"\n" -"Si vous avez besoin d'une aide supplémentaire, veuillez contacter\n" -"par AIM SeanEgn ou LSchiere (en anglais). Les informations pour\n" -"contacter Sean ou Luke en utilisant un autre protocole sont\n" -"disponibles sur\n" -"%swiki/DeveloperPages\n" #. Translators may want to transliterate the name. #. It is not to be translated. @@ -12750,20 +12662,17 @@ "_Commande pour le son :\n" "(%s pour le nom de fichier)" -#, fuzzy msgid "M_ute sounds" -msgstr "_Silencieux" +msgstr "Silencieu_x" msgid "Sounds when conversation has _focus" msgstr "Jouer les sons quand la conversation est en _avant-plan" -#, fuzzy msgid "_Enable sounds:" -msgstr "Activer les sons :" - -#, fuzzy +msgstr "Activer les _sons :" + msgid "V_olume:" -msgstr "Volume :" +msgstr "_Volume :" msgid "Play" msgstr "Jouer" @@ -12984,13 +12893,11 @@ msgid "Custom Smiley Manager" msgstr "Gestionnaire de frimousses personnalisée" -#, fuzzy msgid "Click to change your buddyicon for this account." -msgstr "Utiliser cette _icône pour ce compte :" - -#, fuzzy +msgstr "Cliquer pour changer votre icône pour ce compte." + msgid "Click to change your buddyicon for all accounts." -msgstr "Utiliser cette _icône pour ce compte :" +msgstr "Cliquer pour changer votre icône pour tous les comptes." msgid "Waiting for network connection" msgstr "En attente de connexion réseau." @@ -13128,13 +13035,11 @@ msgid "_Invite" msgstr "_Inviter" -#, fuzzy msgid "_Modify..." -msgstr "_Modifier" - -#, fuzzy +msgstr "_Modifier..." + msgid "_Add..." -msgstr "_Ajouter" +msgstr "_Ajouter..." msgid "_Open Mail" msgstr "_Ouvrir le courrier" @@ -13157,12 +13062,11 @@ msgid "none" msgstr "aucun" -#, fuzzy msgid "Small" -msgstr "Courriel" +msgstr "Petits" msgid "Smaller versions of the default smilies" -msgstr "" +msgstr "Versions de taille inférieure des frimousses par défaut" msgid "Response Probability:" msgstr "Probabilité de réponse :" @@ -13644,9 +13548,8 @@ msgid "Set window manager \"_URGENT\" hint" msgstr "Envoyer le message « _URGENT » au gestionnaire de fenêtres" -#, fuzzy msgid "_Flash window" -msgstr "Les fenêtres de _discussions" +msgstr "_Faire clignoter la fenêtres" #. Raise window method button msgid "R_aise conversation window" @@ -13732,9 +13635,8 @@ msgid "Hyperlink Color" msgstr "Couleur des liens hypertextes" -#, fuzzy msgid "Visited Hyperlink Color" -msgstr "Couleur des liens hypertextes" +msgstr "Couleur des liens visités" msgid "Highlighted Message Name Color" msgstr "Nom de la couleur des messages en surbrillance" @@ -13829,18 +13731,16 @@ #, c-format msgid "You can upgrade to %s %s today." -msgstr "" +msgstr "Vous pouvez mettre à jour %s %s aujourd'hui." msgid "New Version Available" msgstr "Nouvelle version disponible" -#, fuzzy msgid "Later" -msgstr "Date" - -#, fuzzy +msgstr "Plus tard" + msgid "Download Now" -msgstr "Téléchargement de %s : %s" +msgstr "Télécharger maintenant" #. *< type #. *< ui_requirement @@ -14153,334 +14053,44 @@ msgid "This plugin is useful for debbuging XMPP servers or clients." msgstr "Ce plugin est utile pour débugger les clients ou serveurs XMPP." -#~ msgid "A group with the name already exists." -#~ msgstr "Un groupe avec ce nom existe déjà." - -#~ msgid "Primary Information" -#~ msgstr "Informations principales" - -#~ msgid "Blood Type" -#~ msgstr "Groupe sanguin" - -#, fuzzy -#~ msgid "Update information" -#~ msgstr "Mettre à jour mes informations" - -#, fuzzy -#~ msgid "Successed:" -#~ msgstr "Vitesse :" +#~ msgid "Connection to server lost (no data received within %d second)" +#~ msgid_plural "" +#~ "Connection to server lost (no data received within %d seconds)" +#~ msgstr[0] "" +#~ "Connexion avec le serveur perdue (aucune donnée depuis %d seconde)." +#~ msgstr[1] "" +#~ "Connexion avec le serveur perdue (aucune donnée depuis %d secondes)." + +#~ msgid "%d needs Q&A" +#~ msgstr "%d a besoin d'une réponse" + +#~ msgid "Add buddy Q&A" +#~ msgstr "Ajouter une réponse de contact" + +#~ msgid "Can not decrypt get server reply" +#~ msgstr "Impossible de récupérer la réponse du serveur" + +#~ msgid "Keep alive error" +#~ msgstr "Erreur de Keep alive" #~ msgid "" -#~ "Setting custom faces is not currently supported. Please choose an image " -#~ "from %s." -#~ msgstr "" -#~ "Choisir une figure personnalisée ne marche pas encore. Veuillez choisir " -#~ "une image dans %s." - -#~ msgid "Invalid QQ Face" -#~ msgstr "QQ Face non valide" - -#~ msgid "You rejected %d's request" -#~ msgstr "Vous avez rejeté la demande de %d." - -#~ msgid "Reject request" -#~ msgstr "Refus de demande" - -#~ msgid "Add buddy with auth request failed" -#~ msgstr "Ajout d'un contact avec échec d'authentification" - -#, fuzzy -#~ msgid "Add into %d's buddy list" -#~ msgstr "Impossible de charger la liste de contacts." - -#, fuzzy -#~ msgid "QQ Number Error" -#~ msgstr "Numéro QQ" - -#~ msgid "Group Description" -#~ msgstr "Description du groupe" - -#~ msgid "Auth" -#~ msgstr "Authentification" - -#~ msgid "Approve" -#~ msgstr "Approuver" - -#, fuzzy -#~ msgid "Successed to join Qun %d, operated by admin %d" +#~ "Lost connection with server:\n" +#~ "%d, %s" #~ msgstr "" -#~ "Votre demande pour rejoindre le groupe %d a été refusée par " -#~ "l'administrateur %d." - -#, fuzzy -#~ msgid "[%d] removed from Qun \"%d\"" -#~ msgstr "Vous (%d) avez quitté le groupe %d." - -#, fuzzy -#~ msgid "[%d] added to Qun \"%d\"" -#~ msgstr "Vous (%d) avez rejoint le groupe %d." - -#~ msgid "I am a member" -#~ msgstr "J'en suis membre." - -#, fuzzy -#~ msgid "I am requesting" -#~ msgstr "Mauvaise requête" - -#~ msgid "I am the admin" -#~ msgstr "J'en suis administrateur." - -#~ msgid "Unknown status" -#~ msgstr "État inconnu." - -#, fuzzy -#~ msgid "Remove from Qun" -#~ msgstr "Supprimer un groupe" - -#~ msgid "You entered a group ID outside the acceptable range" -#~ msgstr "Vous avez saisi un ID de groupe hors de l'intervalle autorisé." - -#~ msgid "Are you sure you want to leave this Qun?" -#~ msgstr "Êtes-vous sûr de vouloir quitter ce Qun ?" - -#~ msgid "Do you want to approve the request?" -#~ msgstr "Voulez-vous accepter cette demande ?" - -#, fuzzy -#~ msgid "Change Qun member" -#~ msgstr "Téléphone fixe" - -#, fuzzy -#~ msgid "Change Qun information" -#~ msgstr "Informations du salon" - -#, fuzzy -#~ msgid "" -#~ "%s\n" -#~ "\n" -#~ "%s" -#~ msgstr "%s (%s)" - -#~ msgid "System Message" -#~ msgstr "Message système" - -#~ msgid "<b>Last Login IP</b>: %s<br>\n" -#~ msgstr "<b>Adresse IP à la dernière connexion :</b> %s<br>\n" - -#~ msgid "<b>Last Login Time</b>: %s\n" -#~ msgstr "<b>Temps de connexion la dernière fois :</b> %s\n" - -#~ msgid "Set My Information" -#~ msgstr "Changer mes informations" - -#, fuzzy -#~ msgid "Leave the QQ Qun" -#~ msgstr "Quitter ce QQ Qun" - -#~ msgid "Block this buddy" -#~ msgstr "Bloquer ce contact" - -#~ msgid "Invalid token reply code, 0x%02X" -#~ msgstr "Code du token de réponse non valide : 0x%02X" - -#, fuzzy -#~ msgid "Error password: %s" -#~ msgstr "Erreur lors du changement du mot de passe" - -#, fuzzy -#~ msgid "Failed to connect all servers" -#~ msgstr "Impossible de se connecter au serveur." - -#~ msgid "Connecting server %s, retries %d" -#~ msgstr "Connexion au serveur %s, tentatives %d" - -#, fuzzy -#~ msgid "Do you approve the requestion?" -#~ msgstr "Voulez-vous accepter cette demande ?" - -#, fuzzy -#~ msgid "Do you add the buddy?" -#~ msgstr "Voulez-vous ajouter ce contact à votre liste ?" - -#, fuzzy -#~ msgid "%s added you [%s] to buddy list" -#~ msgstr "L'utilisateur %s vous (%s) a ajouté à sa liste de contacts." - -#, fuzzy -#~ msgid "QQ Budy" -#~ msgstr "Contact" - -#~ msgid "%s wants to add you [%s] as a friend" -#~ msgstr "%s veut vous (%s) ajouter en tant que contact." - -#, fuzzy -#~ msgid "%s is not in buddy list" -#~ msgstr "L'utilisateur %s n'est pas dans votre liste de contacts." - -#, fuzzy -#~ msgid "Would you add?" -#~ msgstr "Voulez-vous l'ajouter ?" - -#~ msgid "%s" -#~ msgstr "%s" - -#, fuzzy -#~ msgid "QQ Server Notice" -#~ msgstr "Port du serveur" - -#, fuzzy -#~ msgid "Network disconnected" -#~ msgstr "Correspondant déconnecté" - -#~ msgid "developer" -#~ msgstr "codeur" - -#~ msgid "XMPP developer" -#~ msgstr "codeur XMPP" - -#~ msgid "Artists" -#~ msgstr "Artistes" - -#~ msgid "" -#~ "You are using %s version %s. The current version is %s. You can get it " -#~ "from <a href=\"%s\">%s</a><hr>" -#~ msgstr "" -#~ "Vous utilisez %s version %s. La dernière version est %s. Vous pouvez la " -#~ "télécharger depuis <a href=\"%s\">%s</a><hr>" - -#~ msgid "<b>ChangeLog:</b><br>%s" -#~ msgstr "<b>Nouveautés :</b><br>%s" - -#~ msgid "EOF while reading from resolver process" -#~ msgstr "Fin de fichier à la lecture du processus de résolution de noms." - -#~ msgid "Your information has been updated" -#~ msgstr "Vos informations ont été mises à jour." - -#~ msgid "Input your reason:" -#~ msgstr "Saisissez votre raison :" - -#~ msgid "You have successfully removed a buddy" -#~ msgstr "Vous avez supprimé un contact." - -#~ msgid "You have successfully removed yourself from your friend's buddy list" -#~ msgstr "Vous avez été supprimé de la liste d'un contact." - -#~ msgid "You have added %d to buddy list" -#~ msgstr "Vous avez ajouté l'utilisateur %d à votre liste de contacts." - -#~ msgid "Invalid QQid" -#~ msgstr "QQid non valide" - -#~ msgid "Please enter external group ID" -#~ msgstr "Veuillez saisir un ID de groupe externe." - -#~ msgid "Reason: %s" -#~ msgstr "Raison : %s" - -#~ msgid "Your request to join group %d has been approved by admin %d" -#~ msgstr "" -#~ "Votre demande pour rejoindre le groupe %d a été acceptée par " -#~ "l'administrateur %d." - -#~ msgid "This group has been added to your buddy list" -#~ msgstr "Ce groupe a été ajouté à votre liste de contacts." - -#~ msgid "I am applying to join" -#~ msgstr "Je demande à être membre." - -#~ msgid "You have successfully left the group" -#~ msgstr "Vous avez quitté ce groupe." - -#~ msgid "QQ Group Auth" -#~ msgstr "Authentification QQ du groupe" - -#~ msgid "Your authorization request has been accepted by the QQ server" -#~ msgstr "" -#~ "Votre opération d'authentification a été acceptée par le serveur QQ." - -#~ msgid "Enter your reason:" -#~ msgstr "Saisissez votre raison :" - -#~ msgid " Space" -#~ msgstr "Espace" - -#~ msgid "<b>Real hostname</b>: %s: %d<br>\n" -#~ msgstr "<b>Hôte réel :</b> %s: %d<br>\n" - -#~ msgid "Show Login Information" -#~ msgstr "Afficher les informations de connexion" - -#~ msgid "resend interval(s)" -#~ msgstr "Délai(s) de réenvoi" - -#~ msgid "hostname is NULL or port is 0" -#~ msgstr "Le nom d'hôte est NULL ou le port est 0" - -#~ msgid "Unable to login. Check debug log." -#~ msgstr "" -#~ "Impossible de se connecter, veuillez vérifier les messages de débuggage." - -#~ msgid "Unable to login" -#~ msgstr "Impossible de se connecter." - -#~ msgid "Failed room reply" -#~ msgstr "Échec à la réponse du salon" - -#~ msgid "User %s rejected your request" -#~ msgstr "L'utilisateur %s a refusé votre demande." - -#~ msgid "User %s approved your request" -#~ msgstr "L'utilisateur %s a accepté votre demande." - -#~ msgid "Notice from: %s" -#~ msgstr "Annonce de : %s" - -#~ msgid "Code [0x%02X]: %s" -#~ msgstr "Code [0x%02X] : %s" - -#~ msgid "Group Operation Error" -#~ msgstr "Erreur sur une opération du groupe." - -#~ msgid "Error setting socket options" -#~ msgstr "Erreur à la mise en place des paramètres de la connexion." - -#~ msgid "" -#~ "Windows Live ID authentication: cannot find authenticate token in server " -#~ "response" -#~ msgstr "" -#~ "Authentification Windows Live ID : impossible de trouver le jeton " -#~ "d'authentification dans la réponse du serveur." - -#~ msgid "Windows Live ID authentication Failed" -#~ msgstr "Échec de l'authentification Windows Live ID" - -#~ msgid "Too evil (sender)" -#~ msgstr "Trop méchant (envoyeur)" - -#~ msgid "Too evil (receiver)" -#~ msgstr "Trop méchant (destinataire)" - -#~ msgid "Available Message" -#~ msgstr "Message de disponibilité" - -#~ msgid "Away Message" -#~ msgstr "Message d'absence" - -#~ msgid "<i>(retrieving)</i>" -#~ msgstr " <i>(en cours de récupération)</i>" - -#~ msgid "Error requesting login token" -#~ msgstr "Erreur à la demande d'un token de connexion" - -#~ msgid "TCP Address" -#~ msgstr "Adresse TCP" - -#~ msgid "UDP Address" -#~ msgstr "Adresse UDP" - -#~ msgid "Screen name:" -#~ msgstr "Nom d'utilisateur :" +#~ "Connexion perdue avec le serveur :\n" +#~ "%d, %s" + +#~ msgid "Connecting server ..." +#~ msgstr "Connexion au serveur..." + +#~ msgid "Failed to send IM." +#~ msgstr "Impossible d'envoyer le message." + +#~ msgid "Not a member of room \"%s\"\n" +#~ msgstr "Vous n'êtes pas membre du groupe « %s ».\n" + +#~ msgid "User information for %s unavailable" +#~ msgstr "Les informations sur %s ne sont pas disponibles" #~ msgid "Someone says your screen name in chat" #~ msgstr "Quelqu'un dit votre nom d'utilisateur dans la discussion" @@ -14493,54 +14103,6 @@ #~ "connexion non cryptée. Voulez-vous autoriser ceci et continuer " #~ "l'authentification ?" -#~ msgid "Use GSSAPI (Kerberos v5) for authentication" -#~ msgstr "Utiliser GSSAPI (Kerberos v5) pour l'authentification" - -#~ msgid "Invalid screen name" -#~ msgstr "Nom d'utilisateur non valide." - -#~ msgid "Invalid screen name." -#~ msgstr "Nom d'utilisateur non valide." - -#~ msgid "Screen _name:" -#~ msgstr "_Nom d'utilisateur :" - -#~ msgid "" -#~ "%s%s<span weight=\"bold\">Written by:</span>\t%s\n" -#~ "<span weight=\"bold\">Website:</span>\t\t%s\n" -#~ "<span weight=\"bold\">Filename:</span>\t\t%s" -#~ msgstr "" -#~ "%s%s<span weight=\"bold\">Auteur :</span> %s\n" -#~ "<span weight=\"bold\">Page web :</span> %s\n" -#~ "<span weight=\"bold\">Fichier :</span> %s" - -#~ msgid "" -#~ "The contact availability plugin (cap) is used to display statistical " -#~ "information about buddies in a users contact list." -#~ msgstr "" -#~ "Ce plugin est utilisé pour afficher des informations statistiques à " -#~ "propos des contacts de l'utilisateur." - -#~ msgid "Screen name sent" -#~ msgstr "Nom d'utilisateur envoyé" - -#~ msgid "Screen name" -#~ msgstr "Nom d'utilisateur" - -#~ msgid "_Merge" -#~ msgstr "_Fusionner" - -#~ msgid "" -#~ "Please enter the screen name of the person you would like to add to your " -#~ "buddy list. You may optionally enter an alias, or nickname, for the " -#~ "buddy. The alias will be displayed in place of the screen name whenever " -#~ "possible.\n" -#~ msgstr "" -#~ "Saisissez le nom d'utilisateur de la personne que vous voulez ajouter à " -#~ "votre liste de contacts. Vous pouvez choisir un alias ou surnom pour le " -#~ "contact. L'alias sera affiché à la place du nom d'utilisateur chaque fois " -#~ "que cela est possible.\n" - #~ msgid "A_ccount:" #~ msgstr "_Compte :"