diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..46be8ac --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,15 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((c-mode + (eval . (setq-local flycheck-clang-include-path + (list (expand-file-name "libwebsockets/include" (projectile-project-root)) + (expand-file-name "json-c" (projectile-project-root))))) + (eval . (setq-local company-clang-arguments + (list (concat "-I" (expand-file-name "libwebsockets/include" (projectile-project-root))) + (concat "-I" (expand-file-name "json-c" (projectile-project-root)))))) + (flycheck-clang-warnings . ("all" "extra" "error-implicit-function-declaration" "no-missing-field-initializers")) + (flycheck-clang-language-standard . "gnu99") + (flycheck-checker . c/c++-clang) + (projectile-project-compilation-cmd . "scan-build-3.8 make -j8"))) + diff --git a/Makefile b/Makefile index c9305d0..5f9704a 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ SRCS=slack.c \ api/slack-api-error.c \ api/slack-api-message.c \ api/slack-api-user-typing.c \ + api/message/slack-api-message-bot-message.c \ api/message/slack-api-message-unimplemented.c \ request/slack-request-chat-postmessage.c \ request/slack-request-channels-list.c \ @@ -65,7 +66,7 @@ install: slack.so tags: ctags -f .git/tags -R *.c *.h - + cs: cscope -RUbq diff --git a/api/message/slack-api-message-bot-message.c b/api/message/slack-api-message-bot-message.c new file mode 100644 index 0000000..1036afb --- /dev/null +++ b/api/message/slack-api-message-bot-message.c @@ -0,0 +1,99 @@ +#include +#include + +#include "../../weechat-plugin.h" +#include "../../slack.h" +#include "../../slack-workspace.h" +#include "../../slack-message.h" +#include "../../slack-api.h" +#include "../../slack-channel.h" +#include "../../slack-user.h" +#include "../slack-api-message.h" + +static const char *subtype = "bot_message"; + +static inline int json_valid(json_object *object, struct t_slack_workspace *workspace) +{ + if (!object) + { + weechat_printf( + workspace->buffer, + _("%s%s: error handling websocket %smessage.%s%s message: " + "unexpected response from server"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, + weechat_color("chat_value"), subtype, weechat_color("reset")); + return 0; + } + + return 1; +} + +int slack_api_message_bot_message_handle(struct t_slack_workspace *workspace, + const char *channel, const char *bot_id, + const char *username, const char *text, + const char *ts) +{ + struct t_slack_channel *ptr_channel; + struct t_slack_user *ptr_user; + struct t_slack_channel_typing *ptr_typing; + + ptr_channel = slack_channel_search(workspace, channel); + if (!ptr_channel) + return 1; /* silently ignore if channel hasn't been loaded yet */ + ptr_user = slack_user_bot_search(workspace, bot_id); + if (!ptr_user) + return 1; /* silently ignore if bot user hasn't been loaded yet */ + + char *message = slack_message_decode(workspace, text); + weechat_printf_date_tags( + ptr_channel->buffer, + (time_t)atof(ts), + "slack_message,slack_bot_message", + _("%s%s"), + slack_user_as_prefix(workspace, ptr_user, username), + message); + free(message); + + ptr_typing = slack_channel_typing_search(ptr_channel, + ptr_user->profile.display_name); + if (ptr_typing) + { + slack_channel_typing_free(ptr_channel, ptr_typing); + slack_channel_typing_cb(ptr_channel, NULL, 0); + } + + return 1; +} + +int slack_api_message_bot_message(struct t_slack_workspace *workspace, + json_object *message) +{ + json_object *channel, *bot_id, *username, *text, *ts; + channel = json_object_object_get(message, "channel"); + if (!json_valid(channel, workspace)) + return 0; + + bot_id = json_object_object_get(message, "bot_id"); + if (!json_valid(bot_id, workspace)) + return 0; + + username = json_object_object_get(message, "username"); + if (!json_valid(username, workspace)) + return 0; + + text = json_object_object_get(message, "text"); + if (!json_valid(text, workspace)) + return 0; + + ts = json_object_object_get(message, "ts"); + if (!json_valid(ts, workspace)) + return 0; + + return slack_api_message_bot_message_handle(workspace, + json_object_get_string(channel), + json_object_get_string(bot_id), + json_object_get_string(username), + json_object_get_string(text), + json_object_get_string(ts)); +} + diff --git a/api/message/slack-api-message-bot-message.h b/api/message/slack-api-message-bot-message.h new file mode 100644 index 0000000..a567f22 --- /dev/null +++ b/api/message/slack-api-message-bot-message.h @@ -0,0 +1,8 @@ +#ifndef _SLACK_API_MESSAGE_BOT_MESSAGE_H_ +#define _SLACK_API_MESSAGE_BOT_MESSAGE_H_ + +int slack_api_message_bot_message( + struct t_slack_workspace *workspace, + json_object *message); + +#endif /*SLACK_API_MESSAGE_BOT_MESSAGE_H*/ diff --git a/api/slack-api-message.c b/api/slack-api-message.c index ae6010c..c181e10 100644 --- a/api/slack-api-message.c +++ b/api/slack-api-message.c @@ -10,6 +10,7 @@ #include "../slack-message.h" #include "slack-api-message.h" #include "message/slack-api-message-unimplemented.h" +#include "message/slack-api-message-bot-message.h" static const char *type = "message"; @@ -21,7 +22,7 @@ struct stringcase }; static struct stringcase cases[] = -{ { "bot_message", &slack_api_message_unimplemented } +{ { "bot_message", &slack_api_message_bot_message } , { "channel_archive", &slack_api_message_unimplemented } , { "channel_join", &slack_api_message_unimplemented } , { "channel_leave", &slack_api_message_unimplemented } @@ -97,10 +98,10 @@ int slack_api_message_message_handle(struct t_slack_workspace *workspace, (time_t)atof(ts), "slack_message", _("%s%s"), - slack_user_as_prefix(workspace, ptr_user), + slack_user_as_prefix(workspace, ptr_user, NULL), message); free(message); - + ptr_typing = slack_channel_typing_search(ptr_channel, ptr_user->profile.display_name); if (ptr_typing) diff --git a/request/slack-request-bots-info.c b/request/slack-request-bots-info.c new file mode 100644 index 0000000..bd04431 --- /dev/null +++ b/request/slack-request-bots-info.c @@ -0,0 +1,268 @@ +#include +#include +#include +#include + +#include "../weechat-plugin.h" +#include "../slack.h" +#include "../slack-workspace.h" +#include "../slack-channel.h" +#include "../slack-request.h" +#include "../slack-user.h" +#include "../request/slack-request-bots-info.h" + +static const char *const endpoint = "/api/bots.info?" + "token=%s&bot=%s"; + +static inline int json_valid(json_object *object, struct t_slack_workspace *workspace) +{ + if (!object) + { + weechat_printf( + workspace->buffer, + _("%s%s: error retrieving bot info: unexpected response from server"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + //__asm__("int3"); + return 0; + } + + return 1; +} + +static const struct lws_protocols protocols[]; + +static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct t_slack_request *request = (struct t_slack_request *)user; + struct lws_client_connect_info ccinfo; + + int status; + + switch (reason) + { + /* because we are protocols[0] ... */ + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + weechat_printf( + request->workspace->buffer, + _("%s%s: (%d) error connecting to slack: %s"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, request->idx, + in ? (char *)in : "(null)"); + + weechat_printf( + request->workspace->buffer, + _("%s%s: (%d) reconnecting..."), + weechat_prefix("error"), SLACK_PLUGIN_NAME, request->idx); + + memset(&ccinfo, 0, sizeof(ccinfo)); /* otherwise uninitialized garbage */ + ccinfo.context = request->context; + ccinfo.ssl_connection = LCCSCF_USE_SSL; + ccinfo.port = 443; + ccinfo.address = "slack.com"; + ccinfo.path = request->uri; + ccinfo.host = ccinfo.address; + ccinfo.origin = ccinfo.address; + ccinfo.method = "GET"; + ccinfo.protocol = protocols[0].name; + ccinfo.pwsi = &request->client_wsi; + ccinfo.userdata = request; + + lws_client_connect_via_info(&ccinfo); + break; + + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: + status = lws_http_client_http_response(wsi); + weechat_printf( + request->workspace->buffer, + _("%s%s: (%d) retrieving bot info... (%d)"), + weechat_prefix("network"), SLACK_PLUGIN_NAME, request->idx, + status); + break; + + /* chunks of chunked content, with header removed */ + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: + { + struct t_json_chunk *new_chunk, *last_chunk; + + new_chunk = malloc(sizeof(*new_chunk)); + new_chunk->data = malloc((1024 * sizeof(char)) + 1); + new_chunk->data[0] = '\0'; + new_chunk->next = NULL; + + strncat(new_chunk->data, in, (int)len); + + if (request->json_chunks) + { + for (last_chunk = request->json_chunks; last_chunk->next; + last_chunk = last_chunk->next); + last_chunk->next = new_chunk; + } + else + { + request->json_chunks = new_chunk; + } + } + return 0; /* don't passthru */ + + /* uninterpreted http content */ + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: + { + char buffer[1024 + LWS_PRE]; + char *px = buffer + LWS_PRE; + int lenx = sizeof(buffer) - LWS_PRE; + + if (lws_http_client_read(wsi, &px, &lenx) < 0) + return -1; + } + return 0; /* don't passthru */ + + case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: + { + int chunk_count, i; + char *json_string; + char cursor[64]; + json_object *response, *ok, *error, *members, *metadata; + json_object *user, *id, *name, *profile, *display_name, *next_cursor; + struct t_json_chunk *chunk_ptr; + + chunk_count = 0; + if (request->json_chunks) + { + chunk_count++; + for (chunk_ptr = request->json_chunks; chunk_ptr->next; + chunk_ptr = chunk_ptr->next) + { + chunk_count++; + } + } + + json_string = malloc((1024 * sizeof(char) * chunk_count) + 1); + json_string[0] = '\0'; + + chunk_ptr = request->json_chunks; + for (i = 0; i < chunk_count; i++) + { + strncat(json_string, chunk_ptr->data, 1024); + chunk_ptr = chunk_ptr->next; + + free(request->json_chunks->data); + free(request->json_chunks); + request->json_chunks = chunk_ptr; + } + + weechat_printf( + request->workspace->buffer, + _("%s%s: (%d) got response: %s"), + weechat_prefix("network"), SLACK_PLUGIN_NAME, request->idx, + json_string); + + response = json_tokener_parse(json_string); + ok = json_object_object_get(response, "ok"); + if (!json_valid(ok, request->workspace)) + { + json_object_put(response); + free(json_string); + return 0; + } + + if(json_object_get_boolean(ok)) + { + // TODO: this + } + else + { + error = json_object_object_get(response, "error"); + if (!json_valid(error, request->workspace)) + { + json_object_put(response); + free(json_string); + return 0; + } + + weechat_printf( + request->workspace->buffer, + _("%s%s: (%d) failed to retrieve users: %s"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, request->idx, + json_object_get_string(error)); + } + + json_object_put(response); + free(json_string); + } + /* fallthrough */ + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + request->client_wsi = NULL; + /* Does not doing this cause a leak? + lws_cancel_service(lws_get_context(wsi));*/ /* abort poll wait */ + break; + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +static const struct lws_protocols protocols[] = { + { + "http", + callback_http, + 0, + 0, + }, + { NULL, NULL, 0, 0 } +}; + +struct t_slack_request *slack_request_bots_info( + struct t_slack_workspace *workspace, + const char *token, const char *cursor) +{ + struct t_slack_request *request; + struct lws_context_creation_info ctxinfo; + struct lws_client_connect_info ccinfo; + + request = slack_request_alloc(workspace); + + size_t urilen = snprintf(NULL, 0, endpoint, token, cursor) + 1; + request->uri = malloc(urilen); + snprintf(request->uri, urilen, endpoint, token, cursor); + + memset(&ctxinfo, 0, sizeof(ctxinfo)); /* otherwise uninitialized garbage */ + ctxinfo.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + ctxinfo.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ + ctxinfo.protocols = protocols; + + request->context = lws_create_context(&ctxinfo); + if (!request->context) + { + weechat_printf( + workspace->buffer, + _("%s%s: (%d) error connecting to slack: lws init failed"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, request->idx); + return NULL; + } + else + { + weechat_printf( + workspace->buffer, + _("%s%s: (%d) contacting slack.com:443"), + weechat_prefix("network"), SLACK_PLUGIN_NAME, request->idx); + } + + memset(&ccinfo, 0, sizeof(ccinfo)); /* otherwise uninitialized garbage */ + ccinfo.context = request->context; + ccinfo.ssl_connection = LCCSCF_USE_SSL; + ccinfo.port = 443; + ccinfo.address = "slack.com"; + ccinfo.path = request->uri; + ccinfo.host = ccinfo.address; + ccinfo.origin = ccinfo.address; + ccinfo.method = "GET"; + ccinfo.protocol = protocols[0].name; + ccinfo.pwsi = &request->client_wsi; + ccinfo.userdata = request; + + lws_client_connect_via_info(&ccinfo); + + return request; +} diff --git a/request/slack-request-bots-info.h b/request/slack-request-bots-info.h new file mode 100644 index 0000000..ca23334 --- /dev/null +++ b/request/slack-request-bots-info.h @@ -0,0 +1,8 @@ +#ifndef _SLACK_REQUEST_BOTS_INFO_H_ +#define _SLACK_REQUEST_BOTS_INFO_H_ + +struct t_slack_request *slack_request_bots_info( + struct t_slack_workspace *workspace, + const char *token, const char *cursor); + +#endif /*SLACK_REQUEST_BOTS_INFO_H*/ diff --git a/request/slack-request-users-list.c b/request/slack-request-users-list.c index e344b49..d986fc6 100644 --- a/request/slack-request-users-list.c +++ b/request/slack-request-users-list.c @@ -123,8 +123,10 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, int chunk_count, i; char *json_string; char cursor[64]; - json_object *response, *ok, *error, *members, *metadata; - json_object *user, *id, *name, *profile, *display_name, *next_cursor; + json_object *response, *ok, *error, *members; + json_object *user, *id, *name; + json_object *profile, *display_name, *bot_id; + json_object *metadata, *next_cursor; struct t_json_chunk *chunk_ptr; chunk_count = 0; @@ -184,46 +186,45 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, user = json_object_array_get_idx(members, i - 1); if (!json_valid(user, request->workspace)) { - json_object_put(response); - free(json_string); - return 0; + continue; } id = json_object_object_get(user, "id"); if (!json_valid(id, request->workspace)) { - json_object_put(response); - free(json_string); - return 0; + continue; } name = json_object_object_get(user, "name"); if (!json_valid(name, request->workspace)) { - json_object_put(response); - free(json_string); - return 0; + continue; } profile = json_object_object_get(user, "profile"); if (!json_valid(profile, request->workspace)) { - json_object_put(response); - free(json_string); - return 0; + continue; } display_name = json_object_object_get(profile, "display_name"); if (!json_valid(display_name, request->workspace)) { - json_object_put(response); - free(json_string); - return 0; + continue; } new_user = slack_user_new(request->workspace, json_object_get_string(id), json_object_get_string(display_name)); + + if (!new_user) + __asm__("int3"); + + bot_id = json_object_object_get(profile, "bot_id"); + if (json_valid(bot_id, request->workspace)) + { + new_user->profile.bot_id = strdup(json_object_get_string(bot_id)); + } } metadata = json_object_object_get(response, "response_metadata"); diff --git a/slack-user.c b/slack-user.c index c26aca5..6314866 100644 --- a/slack-user.c +++ b/slack-user.c @@ -14,7 +14,8 @@ const char *slack_user_get_colour(struct t_slack_user *user) } const char *slack_user_as_prefix(struct t_slack_workspace *workspace, - struct t_slack_user *user) + struct t_slack_user *user, + const char *name) { static char result[256]; @@ -22,11 +23,30 @@ const char *slack_user_as_prefix(struct t_slack_workspace *workspace, snprintf(result, sizeof(result), "%s%s\t", slack_user_get_colour(user), - user->profile.display_name); + name ? name : user->profile.display_name); return result; } +struct t_slack_user *slack_user_bot_search(struct t_slack_workspace *workspace, + const char *bot_id) +{ + struct t_slack_user *ptr_user; + + if (!workspace || !bot_id) + return NULL; + + for (ptr_user = workspace->users; ptr_user; + ptr_user = ptr_user->next_user) + { + if (ptr_user->profile.bot_id && + weechat_strcasecmp(ptr_user->profile.bot_id, bot_id) == 0) + return ptr_user; + } + + return NULL; +} + struct t_slack_user *slack_user_search(struct t_slack_workspace *workspace, const char *id) { @@ -72,7 +92,12 @@ struct t_slack_user *slack_user_new(struct t_slack_workspace *workspace, { struct t_slack_user *new_user, *ptr_user; - if (!workspace || !id || !display_name || !display_name[0]) + if (!workspace || !id || !display_name) + { + return NULL; + } + + if (!display_name[0] && strcmp("USLACKBOT", id) == 0) return NULL; if (!workspace->users) @@ -86,7 +111,9 @@ struct t_slack_user *slack_user_new(struct t_slack_workspace *workspace, } if ((new_user = malloc(sizeof(*new_user))) == NULL) + { return NULL; + } new_user->prev_user = workspace->last_user; new_user->next_user = NULL; @@ -112,10 +139,13 @@ struct t_slack_user *slack_user_new(struct t_slack_workspace *workspace, new_user->profile.status_text = NULL; new_user->profile.status_emoji = NULL; new_user->profile.real_name = NULL; - new_user->profile.display_name = strdup(display_name); + new_user->profile.display_name = display_name[0] ? + strdup(display_name) : + strdup("slackbot"); new_user->profile.real_name_normalized = NULL; new_user->profile.email = NULL; new_user->profile.team = NULL; + new_user->profile.bot_id = NULL; new_user->updated = 0; new_user->is_away = 0; diff --git a/slack-user.h b/slack-user.h index 8525bb3..a8a25f6 100644 --- a/slack-user.h +++ b/slack-user.h @@ -11,6 +11,7 @@ struct t_slack_user_profile char *real_name_normalized; char *email; char *team; + char *bot_id; }; struct t_slack_user @@ -46,7 +47,11 @@ struct t_slack_user }; const char *slack_user_as_prefix(struct t_slack_workspace *workspace, - struct t_slack_user *user); + struct t_slack_user *user, + const char *name); + +struct t_slack_user *slack_user_bot_search(struct t_slack_workspace *workspace, + const char *bot_id); struct t_slack_user *slack_user_search(struct t_slack_workspace *workspace, const char *id);