diff --git a/libdiscord.c b/libdiscord.c index 669683d..a0535b9 100644 --- a/libdiscord.c +++ b/libdiscord.c @@ -100,9 +100,41 @@ typedef enum { CHANNEL_DM = 1, CHANNEL_VOICE = 2, CHANNEL_GROUP_DM = 3, - CHANNEL_GUILD_CATEGORY = 4 + CHANNEL_GUILD_CATEGORY = 4, + CHANNEL_GUILD_NEWS = 5, + CHANNEL_GUILD_STORE = 6, + CHANNEL_GUILD_NEWS_THREAD = 10, + CHANNEL_GUILD_PUBLIC_THREAD = 11, + CHANNEL_GUILD_PRIVATE_THREAD = 12, + CHANNEL_GUILD_STAGE_VOICE = 13, } DiscordChannelType; +typedef enum { + MESSAGE_DEFAULT = 0, + MESSAGE_RECIPIENT_ADD = 1, + MESSAGE_RECIPIENT_REMOVE = 2, + MESSAGE_CALL = 3, + MESSAGE_CHANNEL_NAME_CHANGE = 4, + MESSAGE_CHANNEL_ICON_CHANGE = 5, + MESSAGE_CHANNEL_PINNED_MESSAGE = 6, + MESSAGE_GUILD_MEMBER_JOIN = 7, + MESSAGE_USER_PREMIUM_GUILD_SUBSCRIPTION = 8, + MESSAGE_USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9, + MESSAGE_USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10, + MESSAGE_USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11, + MESSAGE_CHANNEL_FOLLOW_ADD = 12, + MESSAGE_GUILD_DISCOVERY_DISQUALIFIED = 14, + MESSAGE_GUILD_DISCOVERY_REQUALIFIED = 15, + MESSAGE_GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16, + MESSAGE_GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17, + MESSAGE_THREAD_CREATED = 18, + MESSAGE_REPLY = 19, + MESSAGE_CHAT_INPUT_COMMAND = 20, + MESSAGE_THREAD_STARTER_MESSAGE = 21, + MESSAGE_GUILD_INVITE_REMINDER = 22, + MESSAGE_CONTEXT_MENU_COMMAND = 23, +} DiscordMessageType; + typedef enum { NOTIFICATIONS_ALL = 0, NOTIFICATIONS_MENTIONS = 1, @@ -116,8 +148,51 @@ typedef enum { GAME_TYPE_LISTENING = 2, GAME_TYPE_WATCHING = 3, GAME_TYPE_CUSTOM_STATUS = 4, + GAME_TYPE_COMPETING = 5, } DiscordGameType; +typedef enum { + PERM_CREATE_INSTANT_INVITE = 0x1, //1 << 0 + PERM_KICK_MEMBERS = 0x2, //1 << 1 + PERM_BAN_MEMBERS = 0x4, //1 << 2 + PERM_ADMINISTRATOR = 0x8, //1 << 3 + PERM_MANAGE_CHANNELS = 0x10, //1 << 4 + PERM_MANAGE_GUILD = 0x20, //1 << 5 + PERM_ADD_REACTIONS = 0x40, //1 << 6 + PERM_VIEW_AUDIT_LOG = 0x80, //1 << 7 + PERM_PRIORITY_SPEAKER = 0x100, //1 << 8 + PERM_STREAM = 0x200, //1 << 9 + PERM_VIEW_CHANNEL = 0x400, //1 << 10 + PERM_SEND_MESSAGES = 0x800, //1 << 11 + PERM_SEND_TTS_MESSAGES = 0x1000, //1 << 12 + PERM_MANAGE_MESSAGES = 0x2000, //1 << 13 + PERM_EMBED_LINKS = 0x4000, //1 << 14 + PERM_ATTACH_FILES = 0x8000, //1 << 15 + PERM_READ_MESSAGE_HISTORY = 0x10000, //1 << 16 + PERM_MENTION_EVERYONE = 0x20000, //1 << 17 + PERM_USE_EXTERNAL_EMOJIS = 0x40000, //1 << 18 + PERM_VIEW_GUILD_INSIGHTS = 0x80000, //1 << 19 + PERM_CONNECT = 0x100000, //1 << 20 + PERM_SPEAK = 0x200000, //1 << 21 + PERM_MUTE_MEMBERS = 0x400000, //1 << 22 + PERM_DEAFEN_MEMBERS = 0x800000, //1 << 23 + PERM_MOVE_MEMBERS = 0x1000000, //1 << 24 + PERM_USE_VAD = 0x2000000, //1 << 25 + PERM_CHANGE_NICKNAME = 0x4000000, //1 << 26 + PERM_MANAGE_NICKNAMES = 0x8000000, //1 << 27 + PERM_MANAGE_ROLES = 0x10000000, //1 << 28 + PERM_MANAGE_WEBHOOKS = 0x20000000, //1 << 29 + PERM_MANAGE_EMOJIS_AND_STICKERS = 0x40000000, //1 << 30 + PERM_USE_APPLICATION_COMMANDS = 0x80000000, //1 << 31 + PERM_REQUEST_TO_SPEAK = 0x100000000, //1 << 32 + PERM_MANAGE_THREADS = 0x400000000, //1 << 34 + PERM_CREATE_PUBLIC_THREADS = 0x800000000, //1 << 35 + PERM_CREATE_PRIVATE_THREADS = 0x1000000000, //1 << 36 + PERM_USE_EXTERNAL_STICKERS = 0x2000000000, //1 << 37 + PERM_SEND_MESSAGES_IN_THREADS = 0x4000000000, //1 << 38 + PERM_START_EMBEDDED_ACTIVITIES = 0x8000000000 //1 << 39 +} DiscordPermissionFlags; + typedef struct { guint64 id; gchar *name; @@ -949,10 +1024,10 @@ discord_print_users(GHashTable *users) PurpleChatUserFlags discord_get_user_flags_from_permissions(DiscordUser *user, guint64 permissions) { - if (permissions & 0x8) { // Admin + if (permissions & PERM_ADMINISTRATOR) { // Admin return PURPLE_CHAT_USER_OP; } - if (permissions & (0x2 | 0x4)) { // Ban or Kick + if (permissions & (PERM_BAN_MEMBERS | PERM_KICK_MEMBERS)) { // Ban or Kick return PURPLE_CHAT_USER_HALFOP; } @@ -987,9 +1062,9 @@ discord_get_user_flags(DiscordAccount *da, DiscordGuild *guild, DiscordUser *use PurpleChatUserFlags this_flag = PURPLE_CHAT_USER_NONE; if (role != NULL) { - if (role->permissions & 0x8) { /* Admin */ + if (role->permissions & PERM_ADMINISTRATOR) { /* Admin */ this_flag = PURPLE_CHAT_USER_OP; - } else if (role->permissions & (0x2 | 0x4)) { /* Ban/kick */ + } else if (role->permissions & (PERM_BAN_MEMBERS | PERM_KICK_MEMBERS)) { /* Ban/kick */ this_flag = PURPLE_CHAT_USER_HALFOP; } } @@ -1066,6 +1141,8 @@ discord_cookies_to_string(DiscordAccount *ya) return g_string_free(str, FALSE); } +static void discord_fetch_url_with_method_delay(DiscordAccount *da, const gchar *method, const gchar *url, const gchar *postdata, DiscordProxyCallbackFunc callback, gpointer user_data, guint delay); + static void discord_response_callback(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data) @@ -1079,6 +1156,23 @@ discord_response_callback(PurpleHttpConnection *http_conn, JsonParser *parser = json_parser_new(); discord_update_cookies(conn->ya, purple_http_response_get_headers_by_name(response, "Set-Cookie")); + int response_code = purple_http_response_get_code(response); + + if (response_code == 429) { + const gchar *retry_after_s = purple_http_response_get_header(response,"Retry-After"); + gdouble retry_after = retry_after_s ? g_ascii_strtod(retry_after_s, NULL) : 5; + PurpleHttpRequest *request = purple_http_conn_get_request(http_conn); + + discord_fetch_url_with_method_delay(conn->ya, + purple_http_request_get_method(request), + purple_http_request_get_url(request), + purple_http_request_get_contents(request), + conn->callback, conn->user_data, + (guint) retry_after*1000); + + g_free(conn); + return; + } body = url_text; body_len = len; @@ -1088,7 +1182,7 @@ discord_response_callback(PurpleHttpConnection *http_conn, conn->callback(conn->ya, NULL, conn->user_data); } - /* connection error - unersolvable dns name, non existing server */ + /* connection error - unresolvable dns name, non existing server */ gchar *error_msg_formatted = g_strdup_printf(_("Connection error: %s."), error_message); purple_connection_error(conn->ya->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg_formatted); g_free(error_msg_formatted); @@ -1192,10 +1286,62 @@ discord_fetch_url_with_method_len(DiscordAccount *ya, const gchar *method, const g_free(cookies); } +typedef struct { + DiscordAccount *ya; + gchar *method; + gchar *url; + gchar *contents; + DiscordProxyCallbackFunc callback; + gpointer user_data; +} DiscordDelayedRequest; + +static gboolean +discord_fetch_url_with_method_delay_cb(gpointer data) +{ + DiscordDelayedRequest *request = data; + discord_fetch_url_with_method_len(request->ya, + request->method, + request->url, + request->contents, + request-> contents ? strlen(request->contents) : 0, + request->callback, + request->user_data); + g_free(request->method); + g_free(request->url); + if (request->contents) { + g_free(request->contents); + } + g_free(request); + + return FALSE; +} + +static void +discord_fetch_url_with_method_delay(DiscordAccount *da, const gchar *method, const gchar *url, const gchar *postdata, DiscordProxyCallbackFunc callback, gpointer user_data, guint delay) +{ + DiscordDelayedRequest *request; + request = g_new0(DiscordDelayedRequest, 1); + request->ya = da; + request->callback = callback; + request->user_data = user_data; + request->method = g_strdup(method); + request->url = g_strdup(url); + request->contents = postdata ? g_strdup(postdata) : NULL; + + purple_timeout_add(delay + 30, discord_fetch_url_with_method_delay_cb, request); +} + +static void +discord_fetch_url_with_delay(DiscordAccount *da, const gchar *url, const gchar *postdata, DiscordProxyCallbackFunc callback, gpointer user_data, guint delay) +{ + discord_fetch_url_with_method_delay(da, (postdata ? "POST" : "GET"), url, postdata, callback, user_data, delay); +} + static void discord_fetch_url_with_method(DiscordAccount *da, const gchar *method, const gchar *url, const gchar *postdata, DiscordProxyCallbackFunc callback, gpointer user_data) { - discord_fetch_url_with_method_len(da, method, url, postdata, postdata ? strlen(postdata) : 0, callback, user_data); + //discord_fetch_url_with_method_len(da, method, url, postdata, postdata ? strlen(postdata) : 0, callback, user_data); + discord_fetch_url_with_method_delay(da, method, url, postdata, callback, user_data, 0); } static void @@ -1790,6 +1936,9 @@ discord_get_real_name(PurpleConnection *pc, gint id, const char *who) return g_strdup(who); } +static gboolean discord_get_room_force_large(DiscordAccount *da, guint64 id); +static gboolean discord_get_room_force_small(DiscordAccount *da, guint64 id); + static void discord_react_cb(DiscordAccount *da, JsonNode *node, gpointer user_data); static gchar * @@ -1887,19 +2036,34 @@ discord_download_image_cb(DiscordAccount *da, JsonNode *node, gpointer user_data static time_t discord_str_to_time(const gchar *str) { gboolean utc = FALSE; - + if (str == NULL || *str == '\0') { return 0; } - + //workaround for libpurple 2.14.7 if (strstr(str, "+00:00")) { utc = TRUE; } - + return purple_str_to_time(str, utc, NULL, NULL, NULL); } +static gboolean +discord_treat_room_as_small(DiscordAccount *da, guint64 room_id, int head_count) +{ + if (discord_get_room_force_small(da, room_id)) { + return TRUE; + } + if (discord_get_room_force_large(da, room_id)) { + return FALSE; + } + if (head_count < purple_account_get_int(da->account, "large-channel-count", 20)) { + return TRUE; + } + return FALSE; +} + static guint64 discord_process_message(DiscordAccount *da, JsonObject *data, unsigned special_type) { @@ -2286,9 +2450,9 @@ discord_process_message(DiscordAccount *da, JsonObject *data, unsigned special_t g_free(reply_txt); } - if (escaped_content && *escaped_content && msg_type != 3) { + if (escaped_content && *escaped_content && msg_type != MESSAGE_CALL) { purple_serv_got_im(da->pc, merged_username, escaped_content, flags, timestamp); - } else if (msg_type == 3) { + } else if (msg_type == MESSAGE_CALL) { gchar *call_txt = g_strdup_printf(_("%s started a call"), merged_username); purple_conversation_write(conv, NULL, call_txt, PURPLE_MESSAGE_SYSTEM, timestamp); g_free(call_txt); @@ -2363,8 +2527,9 @@ discord_process_message(DiscordAccount *da, JsonObject *data, unsigned special_t gboolean mentioned = flags & PURPLE_MESSAGE_NICK; if ((mentioned && purple_account_get_bool(da->account, "open-chat-on-mention", TRUE)) || - (head_count > 0 && head_count < purple_account_get_int(da->account, "large-channel-count", 20))) { - discord_open_chat(da, channel_id, mentioned); + discord_treat_room_as_small(da, channel->id, head_count)) { + //discord_open_chat(da, channel_id, mentioned); + discord_join_chat_by_id(da, channel_id); } gchar *name = NULL; @@ -2402,25 +2567,32 @@ discord_process_message(DiscordAccount *da, JsonObject *data, unsigned special_t gchar *reply_txt = g_strdup_printf("┌──@%s: %s", reply_username ? reply_username : _("Unknown user"), prev_text); g_free(prev_text); - PurpleChatConversation *chatconv = purple_conversations_find_chat(da->pc, discord_chat_hash(channel_id)); - conv = PURPLE_CONVERSATION(chatconv); + if (conv == NULL) { + PurpleChatConversation *chatconv = purple_conversations_find_chat(da->pc, discord_chat_hash(channel_id)); + conv = PURPLE_CONVERSATION(chatconv); + } - purple_conversation_write(conv, NULL, reply_txt, PURPLE_MESSAGE_SYSTEM, time(NULL)); + if (conv != NULL) { + purple_conversation_write_system_message(conv, reply_txt, PURPLE_MESSAGE_SYSTEM); + } g_free(reply_txt); } - if (escaped_content && *escaped_content && msg_type != 7 && msg_type != 3) { + if (escaped_content && *escaped_content && msg_type != MESSAGE_GUILD_MEMBER_JOIN && msg_type != MESSAGE_CALL) { purple_serv_got_chat_in(da->pc, discord_chat_hash(channel_id), name, flags, escaped_content, timestamp); - } else if (msg_type == 7) { + } else if (msg_type == MESSAGE_GUILD_MEMBER_JOIN) { gchar *join_txt = g_strdup_printf(_("%s joined the guild!"), name); - purple_conversation_write(conv, NULL, join_txt, PURPLE_MESSAGE_SYSTEM, timestamp); + if (conv != NULL) + purple_conversation_write(conv, NULL, join_txt, PURPLE_MESSAGE_SYSTEM, timestamp); g_free(join_txt); - return msg_id; - } else if (msg_type == 3) { + //return msg_id; + } else if (msg_type == MESSAGE_CALL) { gchar *call_txt = g_strdup_printf(_("%s started a call"), name); - purple_conversation_write(conv, NULL, call_txt, PURPLE_MESSAGE_SYSTEM, timestamp); + if (conv != NULL) { + purple_conversation_write(conv, NULL, call_txt, PURPLE_MESSAGE_SYSTEM, timestamp); + } g_free(call_txt); - return msg_id; + //return msg_id; } if (attachments) { @@ -2441,12 +2613,14 @@ discord_process_message(DiscordAccount *da, JsonObject *data, unsigned special_t img_context->flags = flags | PURPLE_MESSAGE_IMAGES; img_context->timestamp = timestamp; - PurpleChatConversation *chatconv = purple_conversations_find_chat(da->pc, discord_chat_hash(channel_id)); - conv = PURPLE_CONVERSATION(chatconv); + if (conv == NULL) { + PurpleChatConversation *chatconv = purple_conversations_find_chat(da->pc, discord_chat_hash(channel_id)); + conv = PURPLE_CONVERSATION(chatconv); + } if (conv != NULL) { int head_count = guild ? g_hash_table_size(guild->members) : 0; - if (head_count < purple_account_get_int(da->account, "large-channel-count", 20) || purple_account_get_bool(da->account, "display-images-large-servers", FALSE) ) { + if (discord_treat_room_as_small(da, channel_id, head_count) || purple_account_get_bool(da->account, "display-images-large-servers", FALSE) ) { discord_fetch_url(da, img_context->url, NULL, discord_download_image_cb, img_context); GList *l = conv->logs; if (l != NULL) { @@ -2469,14 +2643,27 @@ discord_process_message(DiscordAccount *da, JsonObject *data, unsigned special_t } if (reactions != NULL) { - PurpleChatConversation *chatconv = purple_conversations_find_chat(da->pc, discord_chat_hash(channel_id)); - conv = PURPLE_CONVERSATION(chatconv); + if (conv == NULL) { + PurpleChatConversation *chatconv = purple_conversations_find_chat(da->pc, discord_chat_hash(channel_id)); + conv = PURPLE_CONVERSATION(chatconv); + } const gchar *someone = "Someone"; - gchar *reaction_str = discord_get_react_text(conv, reactions, someone); + if (conv != NULL) { + gchar *reaction_str = discord_get_react_text(conv, reactions, someone); - if (reaction_str != NULL) { - purple_conversation_write_system_message(conv, reaction_str, PURPLE_MESSAGE_SYSTEM); - g_free(reaction_str); + if (reaction_str != NULL) { + purple_conversation_write_system_message(conv, reaction_str, PURPLE_MESSAGE_SYSTEM); + g_free(reaction_str); + } + } + } + if (msg_type == MESSAGE_THREAD_CREATED) { + if (conv == NULL) { + PurpleChatConversation *chatconv = purple_conversations_find_chat(da->pc, discord_chat_hash(channel_id)); + conv = PURPLE_CONVERSATION(chatconv); + } + if (conv != NULL) { + purple_conversation_write_system_message(conv, _("A new thread has been started!"), PURPLE_MESSAGE_SYSTEM); } } @@ -2817,7 +3004,7 @@ discord_handle_guild_member_update(DiscordAccount *da, guint64 guild_id, JsonObj guint64 permission = discord_compute_permission(da, user, channel); /* must have READ_MESSAGES */ - if ((permission & 0x400)) { + if ((permission & PERM_VIEW_CHANNEL)) { if (user->id == da->self_user_id) { purple_chat_conversation_set_nick(chat, nickname); } @@ -2876,7 +3063,7 @@ discord_process_dispatch(DiscordAccount *da, const gchar *type, JsonObject *data guint64 permission = discord_compute_permission(da, user, channel); /* must have READ_MESSAGES */ - if ((permission & 0x400)) { + if ((permission & PERM_VIEW_CHANNEL)) { if (user->id == da->self_user_id) { purple_chat_conversation_set_nick(chat, nickname); } @@ -3081,7 +3268,7 @@ discord_process_dispatch(DiscordAccount *da, const gchar *type, JsonObject *data guint64 permission = discord_compute_permission(da, user, channel); /* must have READ_MESSAGES */ - if ((permission & 0x400)) { + if ((permission & PERM_VIEW_CHANNEL)) { discord_add_channel_to_blist(da, channel, NULL); } } @@ -3336,7 +3523,7 @@ discord_process_dispatch(DiscordAccount *da, const gchar *type, JsonObject *data guint64 permission = discord_compute_permission(da, user, channel); /* must have READ_MESSAGES */ - if ((permission & 0x400)) { + if ((permission & PERM_VIEW_CHANNEL)) { PurpleChatUserFlags cbflags = discord_get_user_flags_from_permissions(user, permission); gchar *nickname = discord_create_nickname(user, guild, channel); @@ -3672,7 +3859,7 @@ discord_is_channel_visible(DiscordAccount *da, DiscordUser *user, DiscordChannel guint64 permission = discord_compute_permission(da, user, channel); /* must have READ_MESSAGES */ - if (!(permission & 0x400)) + if (!(permission & PERM_VIEW_CHANNEL)) return FALSE; /* Drop voice channels since we don't support them anyway */ @@ -4429,14 +4616,19 @@ discord_got_guilds(DiscordAccount *da, JsonNode *node, gpointer user_data) * TODO: Possible edge case if there are over 100 incoming DMs? */ +static gboolean discord_get_room_history_limiting(DiscordAccount *da, guint64 id); + static void discord_get_history(DiscordAccount *da, const gchar *channel_id, const gchar *last, int count) { gchar *url = g_strdup_printf("https://" DISCORD_API_SERVER "/api/" DISCORD_API_VERSION "/channels/%s/messages?limit=%d&after=%s", channel_id, count ? count : 100, last); DiscordChannel *channel = discord_get_channel_global(da, channel_id); + gboolean is_limited = discord_get_room_history_limiting(da, to_int(channel_id)); - if (count && channel) { + if (channel && !is_limited) { discord_fetch_url(da, url, NULL, discord_got_history_of_room, channel); + } else if (channel) { + discord_fetch_url(da, url, NULL, discord_got_history_static, channel); } else { discord_fetch_url(da, url, NULL, discord_got_history_static, NULL); } @@ -4469,14 +4661,14 @@ discord_got_read_states(DiscordAccount *da, JsonNode *node, gpointer user_data) discord_get_history(da, channel, last_id, mentions * 2); } else { /* TODO: fetch channel history */ - DiscordChannel *dchannel = discord_get_channel_global(da, channel); + DiscordChannel *dchannel = discord_get_channel_global_int(da, to_int(channel)); if (dchannel != NULL) { purple_debug_misc("discord", "%d unhandled mentions in channel %s\n", mentions, dchannel->name); } } } - g_free(last_id); + g_free(last_id_s); } } @@ -5039,7 +5231,7 @@ discord_socket_got_data(gpointer userdata, PurpleSslConnection *conn, PurpleInpu discord_socket_write_json(ya, ya->pending_writes->data); ya->pending_writes = g_slist_delete_link(ya->pending_writes, ya->pending_writes); } - + ya->five_minute_restart = g_timeout_add_seconds(5 * 60, discord_five_minute_restart, ya); } } @@ -5254,9 +5446,9 @@ static gboolean discord_five_minute_restart(gpointer data) { DiscordAccount *da = data; - + discord_start_socket(da); - + return FALSE; } @@ -5287,7 +5479,7 @@ discord_react_cb(DiscordAccount *da, JsonNode *node, gpointer user_data) PurpleConversation *conv = react->conv; gchar *reactor_nick = react->reactor; gchar *emoji_name = react->reaction; - + if (node == NULL) { discord_free_reaction(react); return; @@ -5741,9 +5933,13 @@ discord_get_chat_name(GHashTable *data) static void discord_got_history_of_room(DiscordAccount *da, JsonNode *node, gpointer user_data) { - JsonArray *messages = json_node_get_array(node); DiscordChannel *channel = user_data; g_return_if_fail(channel); + if (json_node_get_node_type(node) != JSON_NODE_ARRAY) { + // Null object? + return; + } + JsonArray *messages = json_node_get_array(node); gint i, len = json_array_get_length(messages); guint64 last_message = channel->last_message_id; @@ -5765,7 +5961,9 @@ discord_got_history_of_room(DiscordAccount *da, JsonNode *node, gpointer user_da if (rolling_last_message_id < last_message) { /* Request the next 100 messages */ gchar *url = g_strdup_printf("https://" DISCORD_API_SERVER "/api/" DISCORD_API_VERSION "/channels/%" G_GUINT64_FORMAT "/messages?limit=100&after=%" G_GUINT64_FORMAT, channel->id, rolling_last_message_id); - discord_fetch_url(da, url, NULL, discord_got_history_of_room, channel); + + discord_fetch_url_with_delay(da, url, NULL, discord_got_history_of_room, channel, 1000); + g_free(url); } } @@ -5786,6 +5984,51 @@ discord_got_history_static(DiscordAccount *da, JsonNode *node, gpointer user_dat } } +static gboolean +discord_get_room_force_large(DiscordAccount *da, guint64 id) +{ + PurpleBlistNode *blistnode = NULL; + gboolean is_large = FALSE; + gchar *channel_id = from_int(id); + + if (channel_id) { + if (g_hash_table_contains(da->one_to_ones, channel_id)) { + return FALSE; + } + + blistnode = PURPLE_BLIST_NODE(purple_blist_find_chat(da->account, channel_id)); + + if (blistnode != NULL) { + is_large = purple_blist_node_get_bool(blistnode, "large_channel"); + } + g_free(channel_id); + } + + return is_large; +} + +static gboolean +discord_get_room_force_small(DiscordAccount *da, guint64 id) +{ + PurpleBlistNode *blistnode = NULL; + gboolean is_small = FALSE; + gchar *channel_id = from_int(id); + + if (channel_id) { + if (g_hash_table_contains(da->one_to_ones, channel_id)) { + return FALSE; + } + blistnode = PURPLE_BLIST_NODE(purple_blist_find_chat(da->account, channel_id)); + + if (blistnode != NULL) { + is_small = purple_blist_node_get_bool(blistnode, "small_channel"); + } + g_free(channel_id); + } + + return is_small; +} + static gboolean discord_get_room_history_limiting(DiscordAccount *da, guint64 id) { @@ -5914,7 +6157,7 @@ discord_compute_permission(DiscordAccount *da, DiscordUser *user, DiscordChannel permissions = discord_permission_role(guild, r, permissions); } - if (permissions & 0x8) + if (permissions & PERM_ADMINISTRATOR) return G_MAXUINT64; // All permissions for admins // Calculate the channel permissions @@ -5968,9 +6211,9 @@ discord_got_channel_info(DiscordAccount *da, JsonNode *node, gpointer user_data) return; } - guint64 tmp = to_int(id); - DiscordChannel *chan = discord_get_channel_global_int(da, tmp); - chatconv = purple_conversations_find_chat(da->pc, discord_chat_hash(tmp)); + guint64 int_id = to_int(id); + DiscordChannel *chan = discord_get_channel_global_int(da, int_id); + chatconv = purple_conversations_find_chat(da->pc, discord_chat_hash(int_id)); if (chatconv == NULL) { return; @@ -5982,6 +6225,20 @@ discord_got_channel_info(DiscordAccount *da, JsonNode *node, gpointer user_data) purple_chat_conversation_set_topic(chatconv, NULL, json_object_get_string_member(channel, "name")); } + if (json_object_has_member(channel, "last_pin_timestamp")) { + guint64 last_message_id = discord_get_room_last_id(da, int_id); + guint64 last_message_time = ((last_message_id >> 22) + 1420070400000)/1000; + + const gchar *last_pin = json_object_get_string_member(channel, "last_pin_timestamp"); + GDateTime *pin_gdtime = g_date_time_new_from_iso8601(last_pin, NULL); + guint64 pin_time = g_date_time_to_unix(pin_gdtime); + g_date_time_unref(pin_gdtime); + + if (pin_time > last_message_time) { + purple_conversation_write_system_message(PURPLE_CONVERSATION(chatconv), "This channel's pinned messages have been updated. Type \"/pinned\" to see them.", PURPLE_MESSAGE_SYSTEM); + } + } + if (json_object_has_member(channel, "recipients")) { // This is a Group DM JsonArray *recipients = json_object_get_array_member(channel, "recipients"); @@ -6039,7 +6296,7 @@ discord_got_channel_info(DiscordAccount *da, JsonNode *node, gpointer user_data) guint64 permission = discord_compute_permission(da, user, chan); /* must have READ_MESSAGES */ - if ((permission & 0x400)) { + if ((permission & PERM_VIEW_CHANNEL)) { PurpleChatUserFlags cbflags = discord_get_user_flags_from_permissions(user, permission); gchar *nickname = discord_create_nickname(user, guild, chan); @@ -6125,12 +6382,8 @@ discord_open_chat(DiscordAccount *da, guint64 id, gboolean present) } static void -discord_join_chat(PurpleConnection *pc, GHashTable *chatdata) +discord_join_chat_by_id(DiscordAccount *da, guint64 id) { - DiscordAccount *da = purple_connection_get_protocol_data(pc); - - guint64 id = to_int(g_hash_table_lookup(chatdata, "id")); - /* Only returns channel when chat was not already joined */ DiscordChannel *channel = discord_open_chat(da, id, TRUE); @@ -6158,6 +6411,16 @@ discord_join_chat(PurpleConnection *pc, GHashTable *chatdata) } +static void +discord_join_chat(PurpleConnection *pc, GHashTable *chatdata) +{ + DiscordAccount *da = purple_connection_get_protocol_data(pc); + + guint64 id = to_int(g_hash_table_lookup(chatdata, "id")); + + discord_join_chat_by_id(da, id); +} + static void discord_got_ack_token(DiscordAccount *da, JsonNode *node, gpointer user_data) { @@ -6640,7 +6903,7 @@ discord_got_avatar(DiscordAccount *da, JsonNode *node, gpointer user_data) } else { purple_buddy_icons_set_for_user(da->account, username, response_dup, response_len, user->avatar); } - + g_free(username); } @@ -6925,6 +7188,46 @@ discord_status_types(PurpleAccount *account) return types; } +static void +discord_toggle_large_handling(PurpleBlistNode *node, gpointer userdata) +{ + DiscordAccount *da = (DiscordAccount *) userdata; + PurpleChat *chat = PURPLE_CHAT(node); + + DiscordChannel *channel = discord_channel_from_chat(da, chat); + + if (channel == NULL) { + return; + } + + /* Toggle the large flag */ + gboolean is_large = purple_blist_node_get_bool(node, "large_channel"); + purple_blist_node_set_bool(node, "large_channel", !is_large); + if (!is_large) { // Unset small flag if we're setting the large flag + purple_blist_node_set_bool(node, "small_channel", FALSE); + } +} + +static void +discord_toggle_small_handling(PurpleBlistNode *node, gpointer userdata) +{ + DiscordAccount *da = (DiscordAccount *) userdata; + PurpleChat *chat = PURPLE_CHAT(node); + + DiscordChannel *channel = discord_channel_from_chat(da, chat); + + if (channel == NULL) { + return; + } + + /* Toggle the small flag */ + gboolean is_small = purple_blist_node_get_bool(node, "small_channel"); + purple_blist_node_set_bool(node, "small_channel", !is_small); + if (!is_small) { // Unset small flag if we're setting the small flag + purple_blist_node_set_bool(node, "large_channel", FALSE); + } +} + static void discord_toggle_history_limit(PurpleBlistNode *node, gpointer userdata) { @@ -6998,6 +7301,7 @@ discord_blist_node_menu(PurpleBlistNode *node) return NULL; GList *m = NULL; + GList *m_size = NULL; /* Grab a DiscordAccount */ PurpleChat *chat = PURPLE_CHAT(node); @@ -7018,6 +7322,22 @@ discord_blist_node_menu(PurpleBlistNode *node) const char *hist_limit_toggle = is_limited ? _("Grab Full History") : _("Limit Grabbed History"); act = purple_menu_action_new(hist_limit_toggle, PURPLE_CALLBACK(discord_toggle_history_limit), da, NULL); m = g_list_append(m, act); + + gboolean is_large = purple_blist_node_get_bool(node, "large_channel"); + const char *large_handle_toggle = is_large ? _("Default") : _("Large Channel"); + act = purple_menu_action_new(large_handle_toggle, PURPLE_CALLBACK(discord_toggle_large_handling), da, NULL); + //m = g_list_append(m, act); + m_size = g_list_append(m_size, act); + + gboolean is_small = purple_blist_node_get_bool(node, "small_channel"); + const char *small_handle_toggle = is_small ? _("Default") : _("Small Channel"); + act = purple_menu_action_new(small_handle_toggle, PURPLE_CALLBACK(discord_toggle_small_handling), da, NULL); + //m = g_list_append(m, act); + m_size = g_list_append(m_size, act); + + const char *size_handle_toggles = _("Force Treat as..."); + act = purple_menu_action_new(size_handle_toggles, NULL, da, m_size); + m = g_list_append(m, act); } return m; diff --git a/purple2compat/http.c b/purple2compat/http.c index acacc0f..31deb0a 100644 --- a/purple2compat/http.c +++ b/purple2compat/http.c @@ -338,7 +338,7 @@ static time_t purple_http_rfc1123_to_time(const gchar *str) g_free(iso_date); return 0; } - + g_free(d_month); t = purple_str_to_time(iso_date, TRUE, NULL, NULL, NULL); @@ -1448,7 +1448,7 @@ static void _purple_http_disconnect(PurpleHttpConnection *hc, if (hc->response_buffer) g_string_free(hc->response_buffer, TRUE); hc->response_buffer = NULL; - + if (hc->gz_stream) purple_http_gz_free(hc->gz_stream); hc->gz_stream = NULL; @@ -2051,13 +2051,13 @@ void purple_http_cookie_jar_set(PurpleHttpCookieJar *cookie_jar, { gchar *escaped_name = g_strdup(purple_url_encode(name)); gchar *escaped_value = NULL; - + if (value) { escaped_value = g_strdup(purple_url_encode(value)); } - + purple_http_cookie_jar_set_ext(cookie_jar, escaped_name, escaped_value, -1); - + g_free(escaped_name); g_free(escaped_value); } @@ -2638,6 +2638,13 @@ void purple_http_request_set_contents(PurpleHttpRequest *request, request->contents_length = length; } +const gchar * purple_http_request_get_contents(PurpleHttpRequest *request) +{ + g_return_val_if_fail(request != NULL, NULL); + + return request->contents; +} + void purple_http_request_set_contents_reader(PurpleHttpRequest *request, PurpleHttpContentReader reader, gsize contents_length, gpointer user_data) { diff --git a/purple2compat/http.h b/purple2compat/http.h index 8f067aa..4b0b8e4 100644 --- a/purple2compat/http.h +++ b/purple2compat/http.h @@ -602,6 +602,15 @@ purple_http_request_get_keepalive_pool(PurpleHttpRequest *request); void purple_http_request_set_contents(PurpleHttpRequest *request, const gchar *contents, gsize length); +/** + * purple_http_request_get_contents: + * @request: The request. + * + * Gets HTTP postdata set for the request. + * + * Returns: The contents. + */ +const gchar * purple_http_request_get_contents(PurpleHttpRequest *request); /** * purple_http_request_set_contents_reader: * @request: The request.