From 67ea361f9f8b2a197e8e52055e4d779a36a876f5 Mon Sep 17 00:00:00 2001 From: dequis Date: Tue, 30 Aug 2016 17:40:19 -0300 Subject: hipchat: Add basic support for personal oauth tokens Fixes trac ticket 1265 - see the comments on that ticket for reasons on why personal tokens and not the usual oauth flow. TL;DR hipchat doesn't allow third party apps to own oauth client secrets. Instead, those are generated when the "addon" is "installed" which requires a flow that is either impossible or too awkward to use in bitlbee. So, after giving up on the right way and telling the users to manage tokens the ugly way, what's left to do is easy, just a few tweaks in the sasl blob and short-circuit most of the actual oauth stuff. I didn't even bother changing the service struct from google. It's not used. This also updates the gtalk SASL X-OAUTH2 code to use GStrings instead of juggling with malloc/strlen/strcpy, and simplifies a bit the way GStrings are used in the equivalent SASL PLAIN code. --- protocols/jabber/jabber.c | 10 ++++-- protocols/jabber/jabber.h | 1 + protocols/jabber/sasl.c | 83 +++++++++++++++++++++++++++++------------------ 3 files changed, 60 insertions(+), 34 deletions(-) (limited to 'protocols') diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index 11a90ff4..36f56fb1 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); } @@ -396,7 +396,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 d76ee08f..db43f205 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -339,6 +339,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 7778af1f..77a2cee0 100644 --- a/protocols/jabber/sasl.c +++ b/protocols/jabber/sasl.c @@ -38,13 +38,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; + int sup_plain = 0, sup_digest = 0, sup_gtalk = 0, sup_anonymous = 0, sup_hipchat_oauth = 0; int want_oauth = FALSE, want_hipchat = FALSE, want_anonymous = FALSE; GString *mechs; @@ -79,6 +82,8 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) sup_anonymous = 1; } else if (c->text && g_strcasecmp(c->text, "X-OAUTH2") == 0) { sup_gtalk = 1; + } else if (c->text && g_strcasecmp(c->text, "X-HIPCHAT-OAUTH2") == 0) { + sup_hipchat_oauth = 1; } if (c->text) { @@ -89,7 +94,7 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) } if (!want_oauth && !sup_plain && !sup_digest) { - if (sup_gtalk) { + if (sup_gtalk || sup_hipchat_oauth) { imcb_error(ic, "This server requires OAuth " "(supported schemes:%s)", mechs->str); } else { @@ -109,22 +114,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); @@ -148,7 +167,6 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) /* The rest will be done later, when we receive a . */ } else if (sup_plain) { - int len; GString *gs; char *username; @@ -173,12 +191,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)) { @@ -427,20 +442,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) @@ -453,9 +477,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; @@ -485,7 +506,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; -- cgit v1.2.3 From a33ee0fbe4c9b87e38ab46990bba16a7fb362bab Mon Sep 17 00:00:00 2001 From: jgeboski Date: Wed, 22 Jun 2016 14:54:52 -0400 Subject: Added an interface for the listing of existing chatrooms Several protocols can provide a list of existing chatrooms that a user is able join. This is crucial for the usage of several protocols, most notably Purple and Facebook. Plugins wishing to support this extended functionality must implement the new prpl->chat_list() function. This implemented function will in most cases send a remote request for the list of chatrooms. Once the list of chatrooms is obtained, a bee_chat_info_t GSList must be created and assigned to the im_connection->chatlist field. Then a call to the bee_chat_list_finish() is needed to display the list to the user. The chat list is maintained entirely by the plugin, so it is important to ensure all pointers related to the chat list remain valid until the chat list is set to NULL. This list is used internally by bitlbee to calculate indexes, which then allows the user to join a chat with an index, rather than some random identifier. It also important to ensure the list is properly freed whenever it is updated, or when the account is disconnect via the prpl->logout() function. On the user interface side of things, the 'chat list' subcommand was recommissioned. For a user to list the existing chat rooms: chat list Afterwards a user can join a chatroom in the list with its index. This extends the functionality of the 'chat add' subcommand by adding in support for the exclamation point operator to denote an index. chat add ! [channel] --- protocols/bee.h | 7 +++++++ protocols/bee_chat.c | 5 +++++ protocols/nogaim.h | 8 ++++++++ 3 files changed, 20 insertions(+) (limited to 'protocols') diff --git a/protocols/bee.h b/protocols/bee.h index d22e4d85..5f47e464 100644 --- a/protocols/bee.h +++ b/protocols/bee.h @@ -83,6 +83,11 @@ typedef struct bee_user { void *data; /* Can be used by the IM module. */ } bee_user_t; +typedef struct bee_chat_info { + char *title; + char *topic; +} bee_chat_info_t; + /* This one's mostly used so save space and make it easier (cheaper) to compare groups of contacts, etc. */ typedef struct bee_group { @@ -184,4 +189,6 @@ G_MODULE_EXPORT int bee_chat_msg(bee_t *bee, struct groupchat *c, const char *ms G_MODULE_EXPORT struct groupchat *bee_chat_by_title(bee_t *bee, struct im_connection *ic, const char *title); G_MODULE_EXPORT void imcb_chat_invite(struct im_connection *ic, const char *name, const char *who, const char *msg); +G_MODULE_EXPORT void bee_chat_list_finish(struct im_connection *ic); + #endif /* __BEE_H__ */ diff --git a/protocols/bee_chat.c b/protocols/bee_chat.c index 2fcb0396..76ed7f85 100644 --- a/protocols/bee_chat.c +++ b/protocols/bee_chat.c @@ -273,3 +273,8 @@ void imcb_chat_invite(struct im_connection *ic, const char *name, const char *wh ic->bee->ui->chat_invite(ic->bee, bu, name, msg); } } + +void bee_chat_list_finish(struct im_connection *ic) +{ + cmd_chat_list_finish(ic); +} diff --git a/protocols/nogaim.h b/protocols/nogaim.h index e5569313..4cba2174 100644 --- a/protocols/nogaim.h +++ b/protocols/nogaim.h @@ -95,6 +95,7 @@ struct im_connection { bee_t *bee; GSList *groupchats; + GSList *chatlist; }; struct groupchat { @@ -262,6 +263,13 @@ struct prpl { /* If null, equivalent to handle_cmp( ic->acc->user, who ) */ gboolean (* handle_is_self) (struct im_connection *, const char *who); + /* This sets/updates the im_connection->chatlist field with a + * bee_chat_info_t GSList. This function should ensure the + * bee_chat_list_finish() function gets called at some point + * after the chat list is completely updated. + */ + void (* chat_list) (struct im_connection *, const char *server); + /* Some placeholders so eventually older plugins may cooperate with newer BitlBees. */ void *resv1; void *resv2; -- cgit v1.2.3 From 725f94253511a0633d94d615f9c28dd6c8da71f9 Mon Sep 17 00:00:00 2001 From: jgeboski Date: Fri, 24 Jun 2016 16:21:42 -0400 Subject: purple: added room listing support --- protocols/purple/bpurple.h | 6 +++ protocols/purple/purple.c | 128 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) (limited to 'protocols') diff --git a/protocols/purple/bpurple.h b/protocols/purple/bpurple.h index 39677b86..ca7cf70e 100644 --- a/protocols/purple/bpurple.h +++ b/protocols/purple/bpurple.h @@ -14,4 +14,10 @@ struct purple_data guint next_request_id; }; +struct purple_roomlist_data +{ + GSList *chats; + gint topic; +}; + #endif /* !BPURPLE_H */ diff --git a/protocols/purple/purple.c b/protocols/purple/purple.c index 58484132..63f6cb8f 100644 --- a/protocols/purple/purple.c +++ b/protocols/purple/purple.c @@ -353,6 +353,21 @@ static void purple_login(account_t *acc) } } +static void purple_chatlist_free(struct im_connection *ic) +{ + bee_chat_info_t *ci; + GSList *l = ic->chatlist; + + while (l) { + ci = l->data; + l = g_slist_delete_link(l, l); + + g_free(ci->title); + g_free(ci->topic); + g_free(ci); + } +} + static void purple_logout(struct im_connection *ic) { struct purple_data *pd = ic->proto_data; @@ -368,6 +383,7 @@ static void purple_logout(struct im_connection *ic) purple_account_set_enabled(pd->account, "BitlBee", FALSE); purple_connections = g_slist_remove(purple_connections, ic); purple_accounts_remove(pd->account); + purple_chatlist_free(ic); g_hash_table_destroy(pd->input_requests); g_free(pd); } @@ -731,6 +747,20 @@ struct groupchat *purple_chat_join(struct im_connection *ic, const char *room, c return imcb_chat_new(ic, room); } +void purple_chat_list(struct im_connection *ic, const char *server) +{ + PurpleRoomlist *list; + struct purple_data *pd = ic->proto_data; + + list = purple_roomlist_get_list(pd->account->gc); + + if (list) { + purple_roomlist_ref(list); + } else { + imcb_log(ic, "Room listing unsupported by this purple plugin"); + } +} + void purple_transfer_request(struct im_connection *ic, file_transfer_t *ft, char *handle); static void purple_ui_init(); @@ -1266,6 +1296,102 @@ static PurplePrivacyUiOps bee_privacy_uiops = prplcb_privacy_deny_removed, /* deny_removed */ }; +static void prplcb_roomlist_create(PurpleRoomlist *list) +{ + struct purple_roomlist_data *rld; + + list->ui_data = rld = g_new0(struct purple_roomlist_data, 1); + rld->topic = -1; +} + +static void prplcb_roomlist_set_fields(PurpleRoomlist *list, GList *fields) +{ + gint topic = -1; + GList *l; + guint i; + PurpleRoomlistField *field; + struct purple_roomlist_data *rld = list->ui_data; + + for (i = 0, l = fields; l; i++, l = l->next) { + field = l->data; + + /* Use the first visible string field as a fallback topic */ + if (i != 0 && topic < 0 && !field->hidden && + field->type == PURPLE_ROOMLIST_FIELD_STRING) { + topic = i; + } + + if ((g_strcasecmp(field->name, "description") == 0) || + (g_strcasecmp(field->name, "topic") == 0)) { + if (field->type == PURPLE_ROOMLIST_FIELD_STRING) { + rld->topic = i; + } + } + } + + if (rld->topic < 0) { + rld->topic = topic; + } +} + +static void prplcb_roomlist_add_room(PurpleRoomlist *list, PurpleRoomlistRoom *room) +{ + bee_chat_info_t *ci; + const char *title; + const char *topic; + GList *fields; + struct purple_roomlist_data *rld = list->ui_data; + + fields = purple_roomlist_room_get_fields(room); + title = purple_roomlist_room_get_name(room); + + if (rld->topic >= 0) { + topic = g_list_nth_data(fields, rld->topic); + } else { + topic = NULL; + } + + ci = g_new(bee_chat_info_t, 1); + ci->title = g_strdup(title); + ci->topic = g_strdup(topic); + rld->chats = g_slist_prepend(rld->chats, ci); +} + +static void prplcb_roomlist_in_progress(PurpleRoomlist *list, gboolean in_progress) +{ + struct im_connection *ic; + struct purple_roomlist_data *rld = list->ui_data; + + if (in_progress) { + return; + } + + ic = purple_ic_by_pa(list->account); + purple_chatlist_free(ic); + + ic->chatlist = g_slist_reverse(rld->chats); + rld->chats = NULL; + + bee_chat_list_finish(ic); + purple_roomlist_unref(list); +} + +static void prplcb_roomlist_destroy(PurpleRoomlist *list) +{ + g_free(list->ui_data); + list->ui_data = NULL; +} + +static PurpleRoomlistUiOps bee_roomlist_uiops = +{ + NULL, /* show_with_account */ + prplcb_roomlist_create, /* create */ + prplcb_roomlist_set_fields, /* set_fields */ + prplcb_roomlist_add_room, /* add_room */ + prplcb_roomlist_in_progress, /* in_progress */ + prplcb_roomlist_destroy, /* destroy */ +}; + static void prplcb_debug_print(PurpleDebugLevel level, const char *category, const char *arg_s) { fprintf(stderr, "DEBUG %s: %s", category, arg_s); @@ -1430,6 +1556,7 @@ static void purple_ui_init() purple_conversations_set_ui_ops(&bee_conv_uiops); purple_request_set_ui_ops(&bee_request_uiops); purple_privacy_set_ui_ops(&bee_privacy_uiops); + purple_roomlist_set_ui_ops(&bee_roomlist_uiops); purple_notify_set_ui_ops(&bee_notify_uiops); purple_accounts_set_ui_ops(&bee_account_uiops); purple_xfers_set_ui_ops(&bee_xfer_uiops); @@ -1527,6 +1654,7 @@ void purple_initmodule() funcs.chat_kick = purple_chat_kick; funcs.chat_leave = purple_chat_leave; funcs.chat_join = purple_chat_join; + funcs.chat_list = purple_chat_list; funcs.transfer_request = purple_transfer_request; help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n"); -- cgit v1.2.3 From ad541494dc84e51b0f432dc2081653532b245939 Mon Sep 17 00:00:00 2001 From: dequis Date: Tue, 20 Sep 2016 21:53:40 -0300 Subject: twitter: extended tweet support (AKA avoid showing truncated ones) Documentation over here: https://dev.twitter.com/overview/api/upcoming-changes-to-tweets This is already live in twitter, can be tested by including an attachment from twitter web and then fill 140 characters. Should be sanely backwards compatible with twitter clones - i'd expect them to ignore the tweet_mode=extended parameter in REST queries, and just not deliver extended_tweet objects / full_text strings at all. --- protocols/twitter/twitter_lib.c | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 41d95db8..f1274952 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -598,8 +598,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; @@ -607,9 +608,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) { @@ -635,12 +639,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); } @@ -1478,18 +1483,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"); @@ -1500,7 +1507,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]); } } @@ -1515,7 +1522,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"; @@ -1527,9 +1534,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"); -- cgit v1.2.3 From 524e931bb1a61dd7efa44faeb050e41bfc231610 Mon Sep 17 00:00:00 2001 From: Eion Robb Date: Sun, 25 Sep 2016 03:53:52 +1300 Subject: purple: support setting chat room topics (#84) --- protocols/purple/purple.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'protocols') diff --git a/protocols/purple/purple.c b/protocols/purple/purple.c index 63f6cb8f..6c8ddf0d 100644 --- a/protocols/purple/purple.c +++ b/protocols/purple/purple.c @@ -683,6 +683,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; @@ -1651,6 +1666,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; -- cgit v1.2.3