diff options
-rw-r--r-- | debian/bitlbee-common.postinst | 2 | ||||
-rw-r--r-- | doc/user-guide/commands.xml | 19 | ||||
-rw-r--r-- | doc/user-guide/genhelp.py | 3 | ||||
-rw-r--r-- | protocols/jabber/jabber.c | 10 | ||||
-rw-r--r-- | protocols/jabber/jabber.h | 1 | ||||
-rw-r--r-- | protocols/jabber/sasl.c | 83 | ||||
-rw-r--r-- | protocols/purple/purple.c | 16 | ||||
-rw-r--r-- | protocols/twitter/twitter_lib.c | 35 |
8 files changed, 117 insertions, 52 deletions
diff --git a/debian/bitlbee-common.postinst b/debian/bitlbee-common.postinst index de814011..e0e371d8 100644 --- a/debian/bitlbee-common.postinst +++ b/debian/bitlbee-common.postinst @@ -63,7 +63,7 @@ if [ -e /usr/share/bitlbee/help.upgrading ]; then fi fi -if [ "$BITLBEE_UPGRADE_DONT_RESTART" != "1" -a -n "$2" ]; then +if [ "$BITLBEE_UPGRADE_DONT_RESTART" != "1" -a -n "$2" -a -x "/etc/init.d/bitlbee" ]; then invoke-rc.d bitlbee restart fi diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml index fa36d6b7..ef1023cd 100644 --- a/doc/user-guide/commands.xml +++ b/doc/user-guide/commands.xml @@ -257,7 +257,7 @@ <description> <para> - Available actions: add, with. See <emphasis>help chat <action></emphasis> for more information. + Available actions: add, with, list. See <emphasis>help chat <action></emphasis> for more information. </para> </description> @@ -271,7 +271,7 @@ </para> <para> - After adding a room to your list, you can simply use the IRC /join command to enter the room. Also, you can tell BitlBee to automatically join the room when you log in. (See <emphasis>chat set</emphasis>) + After adding a room to your list, you can simply use the IRC /join command to enter the room. Also, you can tell BitlBee to automatically join the room when you log in. (<emphasis>channel <channel> set auto_join true</emphasis>) </para> <para> @@ -286,9 +286,18 @@ <description> <para> - List existing chatrooms provided by an account. BitlBee needs this to propogate an internal list of chats. The existing chat can then be added with <emphasis>chat add</emphasis>. + List existing chatrooms provided by an account. BitlBee needs this to propogate an internal list of chats. The existing chat can then be added with <emphasis>chat add</emphasis>, using the number in the index column after a "!" as a shortcut. </para> </description> + + <ircexample> + <ircline nick="dx">chat list facebook</ircline> + <ircline pre="1" nick="root">Index Title Topic</ircline> + <ircline pre="1" nick="root"> 1 869891016470949 cool kids club</ircline> + <ircline pre="1" nick="root"> 2 457892181062459 uncool kids club</ircline> + <ircline nick="root">2 facebook chatrooms</ircline> + <ircline nick="dx">chat add facebook !1 #cool-kids-club</ircline> + </ircexample> </bitlbee-command> <bitlbee-command name="with"> @@ -298,6 +307,10 @@ <para> While most <emphasis>chat</emphasis> subcommands are about named chatrooms, this command can be used to open an unnamed groupchat with one or more persons. This command is what <emphasis>/join #nickname</emphasis> used to do in older BitlBee versions. </para> + + <para> + Another way to do this is to join to a new, empty channel with <emphasis>/join #newchannel</emphasis> and invite the first person with <emphasis>/invite nickname</emphasis> + </para> </description> </bitlbee-command> </bitlbee-command> diff --git a/doc/user-guide/genhelp.py b/doc/user-guide/genhelp.py index ec619344..e9a3b2bf 100644 --- a/doc/user-guide/genhelp.py +++ b/doc/user-guide/genhelp.py @@ -64,7 +64,8 @@ def fix_tree(tag, debug=False, lvl=''): print("%s</%s>%r" % (lvl, tag.tag, [tag.tail, normalize(tag.tail)])) # Actually normalize whitespace - tag.text = normalize(tag.text) + if 'pre' not in tag.attrib: + tag.text = normalize(tag.text) tag.tail = normalize(tag.tail) diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index fcd90598..05bd9cf9 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -81,11 +81,11 @@ static void jabber_init(account_t *acc) s = set_add(&acc->set, "server", NULL, set_eval_account, acc); s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY | SET_NULL_OK; + set_add(&acc->set, "oauth", "false", set_eval_oauth, acc); + if (strcmp(acc->prpl->name, "hipchat") == 0) { set_setstr(&acc->set, "server", "chat.hipchat.com"); } else { - set_add(&acc->set, "oauth", "false", set_eval_oauth, acc); - /* this reuses set_eval_oauth, which clears the password */ set_add(&acc->set, "anonymous", "false", set_eval_oauth, acc); } @@ -402,7 +402,11 @@ static int jabber_buddy_msg(struct im_connection *ic, char *who, char *message, if (g_strcasecmp(who, JABBER_OAUTH_HANDLE) == 0 && !(jd->flags & OPT_LOGGED_IN) && jd->fd == -1) { - if (sasl_oauth2_get_refresh_token(ic, message)) { + + if (jd->flags & JFLAG_HIPCHAT) { + sasl_oauth2_got_token(ic, message, NULL, NULL); + return 1; + } else if (sasl_oauth2_get_refresh_token(ic, message)) { return 1; } else { imcb_error(ic, "OAuth failure"); diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index c97c5b71..0804ebd7 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -357,6 +357,7 @@ gboolean sasl_supported(struct im_connection *ic); void sasl_oauth2_init(struct im_connection *ic); int sasl_oauth2_get_refresh_token(struct im_connection *ic, const char *msg); int sasl_oauth2_refresh(struct im_connection *ic, const char *refresh_token); +void sasl_oauth2_got_token(gpointer data, const char *access_token, const char *refresh_token, const char *error); extern const struct oauth2_service oauth2_service_google; diff --git a/protocols/jabber/sasl.c b/protocols/jabber/sasl.c index f0381ee8..d4556811 100644 --- a/protocols/jabber/sasl.c +++ b/protocols/jabber/sasl.c @@ -42,13 +42,16 @@ const struct oauth2_service oauth2_service_google = "6C-Zgf7Tr7gEQTPlBhMUgo7R", }; +/* """"""""""""""""""""""""""""""oauth"""""""""""""""""""""""""""""" */ +#define HIPCHAT_SO_CALLED_OAUTH_URL "https://hipchat.com/account/api" + xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) { struct im_connection *ic = data; struct jabber_data *jd = ic->proto_data; struct xt_node *c, *reply; char *s; - int sup_plain = 0, sup_digest = 0, sup_gtalk = 0, sup_anonymous = 0, sup_scram = 0; + int sup_plain = 0, sup_digest = 0, sup_gtalk = 0, sup_anonymous = 0, sup_scram = 0, sup_hipchat_oauth = 0; int want_oauth = FALSE, want_hipchat = FALSE, want_anonymous = FALSE; GString *mechs; @@ -84,6 +87,8 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) sup_anonymous = 1; } else if (g_strcasecmp(c->text, "X-OAUTH2") == 0) { sup_gtalk = 1; + } else if (g_strcasecmp(c->text, "X-HIPCHAT-OAUTH2") == 0) { + sup_hipchat_oauth = 1; } else if (g_strcasecmp(c->text, "SCRAM-SHA-1") == 0) { sup_scram |= JSCRAM_SHA1; } else if (g_strcasecmp(c->text, "SCRAM-SHA-256") == 0) { @@ -97,7 +102,7 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) } if (!want_oauth && !want_anonymous && !sup_plain && !sup_digest && !sup_scram) { - if (sup_gtalk) { + if (sup_gtalk || sup_hipchat_oauth) { imcb_error(ic, "This server requires OAuth " "(supported schemes:%s)", mechs->str); } else { @@ -117,22 +122,36 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) xt_add_attr(reply, "xmlns", XMLNS_HIPCHAT); } - if (sup_gtalk && want_oauth) { - int len; + if ((sup_gtalk || sup_hipchat_oauth) && want_oauth) { + GString *gs; + + gs = g_string_sized_new(128); + + g_string_append_c(gs, '\0'); + + if (sup_gtalk) { + /* X-OAUTH2 is not *the* standard OAuth2 SASL/XMPP implementation. + It's currently used by GTalk and vaguely documented on + http://code.google.com/apis/cloudprint/docs/rawxmpp.html */ + xt_add_attr(reply, "mechanism", "X-OAUTH2"); - /* X-OAUTH2 is, not *the* standard OAuth2 SASL/XMPP implementation. - It's currently used by GTalk and vaguely documented on - http://code.google.com/apis/cloudprint/docs/rawxmpp.html . */ - xt_add_attr(reply, "mechanism", "X-OAUTH2"); + g_string_append(gs, jd->username); + g_string_append_c(gs, '\0'); + g_string_append(gs, jd->oauth2_access_token); + } else if (sup_hipchat_oauth) { + /* Hipchat's variant, not standard either, is documented here: + https://docs.atlassian.com/hipchat.xmpp/latest/xmpp/auth.html */ + xt_add_attr(reply, "mechanism", "oauth2"); + + g_string_append(gs, jd->oauth2_access_token); + g_string_append_c(gs, '\0'); + g_string_append(gs, set_getstr(&ic->acc->set, "resource")); + } - len = strlen(jd->username) + strlen(jd->oauth2_access_token) + 2; - s = g_malloc(len + 1); - s[0] = 0; - strcpy(s + 1, jd->username); - strcpy(s + 2 + strlen(jd->username), jd->oauth2_access_token); - reply->text = base64_encode((unsigned char *) s, len); + reply->text = base64_encode((unsigned char *) gs->str, gs->len); reply->text_len = strlen(reply->text); - g_free(s); + g_string_free(gs, TRUE); + } else if (want_oauth) { imcb_error(ic, "OAuth requested, but not supported by server"); imc_logout(ic, FALSE); @@ -192,7 +211,6 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) /* The rest will be done later, when we receive a <challenge/>. */ } else if (sup_plain) { - int len; GString *gs; char *username; @@ -217,12 +235,9 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) g_string_append(gs, set_getstr(&ic->acc->set, "resource")); } - len = gs->len; - s = g_string_free(gs, FALSE); - - reply->text = base64_encode((unsigned char *) s, len); + reply->text = base64_encode((unsigned char *) gs->str, gs->len); reply->text_len = strlen(reply->text); - g_free(s); + g_string_free(gs, TRUE); } if (reply && !jabber_write_packet(ic, reply)) { @@ -660,20 +675,29 @@ gboolean sasl_supported(struct im_connection *ic) void sasl_oauth2_init(struct im_connection *ic) { struct jabber_data *jd = ic->proto_data; - char *msg, *url; imcb_log(ic, "Starting OAuth authentication"); /* Temporary contact, just used to receive the OAuth response. */ imcb_add_buddy(ic, JABBER_OAUTH_HANDLE, NULL); - url = oauth2_url(jd->oauth2_service); - msg = g_strdup_printf("Open this URL in your browser to authenticate: %s", url); - imcb_buddy_msg(ic, JABBER_OAUTH_HANDLE, msg, 0, 0); + + if (jd->flags & JFLAG_HIPCHAT) { + imcb_buddy_msg(ic, JABBER_OAUTH_HANDLE, + "Open this URL and generate a token with 'View Group' and 'Send Message' scopes: " + HIPCHAT_SO_CALLED_OAUTH_URL, 0, 0); + } else { + char *msg, *url; + + url = oauth2_url(jd->oauth2_service); + msg = g_strdup_printf("Open this URL in your browser to authenticate: %s", url); + imcb_buddy_msg(ic, JABBER_OAUTH_HANDLE, msg, 0, 0); + + g_free(msg); + g_free(url); + } imcb_buddy_msg(ic, JABBER_OAUTH_HANDLE, "Respond to this message with the returned " "authorization token.", 0, 0); - g_free(msg); - g_free(url); } static gboolean sasl_oauth2_remove_contact(gpointer data, gint fd, b_input_condition cond) @@ -686,9 +710,6 @@ static gboolean sasl_oauth2_remove_contact(gpointer data, gint fd, b_input_condi return FALSE; } -static void sasl_oauth2_got_token(gpointer data, const char *access_token, const char *refresh_token, - const char *error); - int sasl_oauth2_get_refresh_token(struct im_connection *ic, const char *msg) { struct jabber_data *jd = ic->proto_data; @@ -718,7 +739,7 @@ int sasl_oauth2_refresh(struct im_connection *ic, const char *refresh_token) refresh_token, sasl_oauth2_got_token, ic); } -static void sasl_oauth2_got_token(gpointer data, const char *access_token, const char *refresh_token, const char *error) +void sasl_oauth2_got_token(gpointer data, const char *access_token, const char *refresh_token, const char *error) { struct im_connection *ic = data; struct jabber_data *jd; diff --git a/protocols/purple/purple.c b/protocols/purple/purple.c index 87d628c3..d87880b3 100644 --- a/protocols/purple/purple.c +++ b/protocols/purple/purple.c @@ -697,6 +697,21 @@ void purple_chat_invite(struct groupchat *gc, char *who, char *message) who); } +void purple_chat_set_topic(struct groupchat *gc, char *topic) +{ + PurpleConversation *pc = gc->data; + PurpleConvChat *pcc = PURPLE_CONV_CHAT(pc); + struct purple_data *pd = gc->ic->proto_data; + PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id); + PurplePluginProtocolInfo *pi = prpl->info->extra_info; + + if (pi->set_chat_topic) { + pi->set_chat_topic(purple_account_get_connection(pd->account), + purple_conv_chat_get_id(pcc), + topic); + } +} + void purple_chat_kick(struct groupchat *gc, char *who, const char *message) { PurpleConversation *pc = gc->data; @@ -1665,6 +1680,7 @@ void purple_initmodule() funcs.chat_msg = purple_chat_msg; funcs.chat_with = purple_chat_with; funcs.chat_invite = purple_chat_invite; + funcs.chat_topic = purple_chat_set_topic; funcs.chat_kick = purple_chat_kick; funcs.chat_leave = purple_chat_leave; funcs.chat_join = purple_chat_join; diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 0750e135..8cd12055 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -637,8 +637,9 @@ static void expand_entities(char **text, const json_value *node); */ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node) { - struct twitter_xml_status *txs; + struct twitter_xml_status *txs = {0}; const json_value *rt = NULL; + const json_value *text_value = NULL; if (node->type != json_object) { return FALSE; @@ -646,9 +647,12 @@ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node) txs = g_new0(struct twitter_xml_status, 1); JSON_O_FOREACH(node, k, v) { - if (strcmp("text", k) == 0 && v->type == json_string) { - txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); - strip_html(txs->text); + if (strcmp("text", k) == 0 && v->type == json_string && text_value == NULL) { + text_value = v; + } else if (strcmp("full_text", k) == 0 && v->type == json_string) { + text_value = v; + } else if (strcmp("extended_tweet", k) == 0 && v->type == json_object) { + text_value = json_o_get(v, "full_text"); } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) { rt = v; } else if (strcmp("created_at", k) == 0 && v->type == json_string) { @@ -674,12 +678,13 @@ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node) if (rt) { struct twitter_xml_status *rtxs = twitter_xt_get_status(rt); if (rtxs) { - g_free(txs->text); txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); txs->id = rtxs->id; txs_free(rtxs); } - } else { + } else if (text_value && text_value->type == json_string) { + txs->text = g_memdup(text_value->u.string.ptr, text_value->u.string.length + 1); + strip_html(txs->text); expand_entities(&txs->text, node); } @@ -1546,18 +1551,20 @@ static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_curs td->home_timeline_obj = NULL; td->flags &= ~TWITTER_GOT_TIMELINE; - char *args[6]; + char *args[8]; args[0] = "cursor"; args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor); args[2] = "include_entities"; args[3] = "true"; + args[4] = "tweet_mode"; + args[5] = "extended"; if (td->timeline_id) { - args[4] = "since_id"; - args[5] = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id); + args[6] = "since_id"; + args[7] = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id); } if (twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args, - td->timeline_id ? 6 : 4) == NULL) { + td->timeline_id ? 8 : 6) == NULL) { if (++td->http_fails >= 5) { imcb_error(ic, "Could not retrieve %s: %s", TWITTER_HOME_TIMELINE_URL, "connection failed"); @@ -1568,7 +1575,7 @@ static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_curs g_free(args[1]); if (td->timeline_id) { - g_free(args[5]); + g_free(args[7]); } } @@ -1583,7 +1590,7 @@ static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor) td->mentions_obj = NULL; td->flags &= ~TWITTER_GOT_MENTIONS; - char *args[6]; + char *args[8]; args[0] = "cursor"; args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor); args[2] = "include_entities"; @@ -1595,9 +1602,11 @@ static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor) args[4] = "count"; args[5] = g_strdup_printf("%d", set_getint(&ic->acc->set, "show_old_mentions")); } + args[6] = "tweet_mode"; + args[7] = "extended"; if (twitter_http(ic, TWITTER_MENTIONS_URL, twitter_http_get_mentions, - ic, 0, args, 6) == NULL) { + ic, 0, args, 8) == NULL) { if (++td->http_fails >= 5) { imcb_error(ic, "Could not retrieve %s: %s", TWITTER_MENTIONS_URL, "connection failed"); |