aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--debian/bitlbee-common.postinst2
-rw-r--r--doc/user-guide/commands.xml19
-rw-r--r--doc/user-guide/genhelp.py3
-rw-r--r--protocols/jabber/jabber.c10
-rw-r--r--protocols/jabber/jabber.h1
-rw-r--r--protocols/jabber/sasl.c83
-rw-r--r--protocols/purple/purple.c16
-rw-r--r--protocols/twitter/twitter_lib.c35
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 &lt;action&gt;</emphasis> for more information.
+ Available actions: add, with, list. See <emphasis>help chat &lt;action&gt;</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 &lt;channel&gt; 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");