From 22f22a0aeef65567a8ced0f0f98c71cdc1f3aad3 Mon Sep 17 00:00:00 2001 From: Tony Olagbaiye Date: Fri, 4 May 2018 01:31:06 +0100 Subject: [PATCH] frankly i'm not sure --- .gitignore | 1 + Makefile | 14 +- api/slack-api-message.c | 17 +- request/slack-request-chat-postmessage.c | 244 +++++++++++++++++++++++ request/slack-request-chat-postmessage.h | 9 + slack-api.c | 4 +- slack-buffer.c | 2 +- slack-buffer.h | 4 + slack-input.c | 57 +++++- slack-message.c | 139 +++++++++++++ slack-message.h | 9 + slack-user.c | 20 ++ slack-user.h | 3 + slack-workspace.c | 2 +- slack.c | 2 +- 15 files changed, 512 insertions(+), 15 deletions(-) create mode 100644 request/slack-request-chat-postmessage.c create mode 100644 request/slack-request-chat-postmessage.h create mode 100644 slack-message.c create mode 100644 slack-message.h diff --git a/.gitignore b/.gitignore index 4e9fb6b..771d19f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # Prerequisites +cscope* .depend *.d diff --git a/Makefile b/Makefile index 6e0eb15..ee1280a 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ CC=clang CXX=clang++ RM=rm -f -CFLAGS=-fPIC -std=gnu99 -g -Wall -Wextra -Werror-implicit-function-declaration -Wno-missing-field-initializers -I libwebsockets/include -I json-c -LDFLAGS=-shared -g +CFLAGS=-fsanitize=address -fno-omit-frame-pointer -fPIC -std=gnu99 -g -Wall -Wextra -Werror-implicit-function-declaration -Wno-missing-field-initializers -I libwebsockets/include -I json-c +LDFLAGS=-shared -g -fsanitize=address LDLIBS=-lgnutls SRCS=slack.c \ @@ -12,6 +12,7 @@ SRCS=slack.c \ slack-config.c \ slack-command.c \ slack-input.c \ + slack-message.c \ slack-oauth.c \ slack-request.c \ slack-teaminfo.c \ @@ -21,6 +22,7 @@ SRCS=slack.c \ api/slack-api-error.c \ api/slack-api-message.c \ api/message/slack-api-message-unimplemented.c \ + request/slack-request-chat-postmessage.c \ request/slack-request-channels-list.c \ request/slack-request-users-list.c OBJS=$(subst .c,.o,$(SRCS)) libwebsockets/lib/libwebsockets.a json-c/libjson-c.a @@ -52,4 +54,12 @@ clean: distclean: clean $(RM) *~ .depend +.PHONY: tags cs + +tags: + ctags -f .git/tags -R *.c *.h + +cs: + cscope -RUbq + include .depend diff --git a/api/slack-api-message.c b/api/slack-api-message.c index fb385ca..3ea0514 100644 --- a/api/slack-api-message.c +++ b/api/slack-api-message.c @@ -7,6 +7,7 @@ #include "../slack-api.h" #include "../slack-channel.h" #include "../slack-user.h" +#include "../slack-message.h" #include "slack-api-message.h" #include "message/slack-api-message-unimplemented.h" @@ -82,8 +83,6 @@ int slack_api_message_message_handle(struct t_slack_workspace *workspace, struct t_slack_channel *ptr_channel; struct t_slack_user *ptr_user; - (void) ts; - ptr_channel = slack_channel_search(workspace, channel); if (!ptr_channel) return 1; /* silently ignore if channel hasn't been loaded yet */ @@ -91,11 +90,15 @@ int slack_api_message_message_handle(struct t_slack_workspace *workspace, if (!ptr_user) return 1; /* silently ignore if user hasn't been loaded yet */ - weechat_printf( - workspace->buffer, - _("%s%s: message [%s]: <%s> %s"), - weechat_prefix("error"), SLACK_PLUGIN_NAME, - ptr_channel->name, ptr_user->profile.display_name, text); + char *message = slack_message_decode(workspace, text); + weechat_printf_date_tags( + ptr_channel->buffer, + (time_t)atof(ts), + "slack_message", + _("%s%s"), + slack_user_as_prefix(workspace, ptr_user), + message); + free(message); return 1; } diff --git a/request/slack-request-chat-postmessage.c b/request/slack-request-chat-postmessage.c new file mode 100644 index 0000000..d795a9c --- /dev/null +++ b/request/slack-request-chat-postmessage.c @@ -0,0 +1,244 @@ +#include +#include +#include +#include + +#include "../weechat-plugin.h" +#include "../slack.h" +#include "../slack-workspace.h" +#include "../slack-request.h" +#include "../slack-user.h" +#include "../request/slack-request-chat-postmessage.h" + +static const char *const endpoint = "/api/chat.postMessage?" + "token=%s&channel=%s&text=%s&" + "as_user=true&link_names=true&mrkdwn=false&parse=full"; + +static inline int json_valid(json_object *object, struct t_slack_workspace *workspace) +{ + if (!object) + { + weechat_printf( + workspace->buffer, + _("%s%s: error posting message: unexpected response from server"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + //__asm__("int3"); + return 0; + } + + return 1; +} + +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; + + 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)"); + request->client_wsi = NULL; + break; + + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: + status = lws_http_client_http_response(wsi); + weechat_printf( + request->workspace->buffer, + _("%s%s: (%d) posting message... (%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; + json_object *response, *ok, *error; + 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)) + { + /* wait for websocket to catch up */ + } + 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 post message: %s"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, request->idx, + json_object_get_string(error)); + } + + json_object_put(response); + free(json_string); + } + 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_chat_postmessage( + struct t_slack_workspace *workspace, + const char *token, const char *channel, + const char *text) +{ + 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, channel, text) + 1; + request->uri = malloc(urilen); + snprintf(request->uri, urilen, endpoint, token, channel, text); + + 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-chat-postmessage.h b/request/slack-request-chat-postmessage.h new file mode 100644 index 0000000..48d31fc --- /dev/null +++ b/request/slack-request-chat-postmessage.h @@ -0,0 +1,9 @@ +#ifndef _SLACK_REQUEST_CHAT_POSTMESSAGE_H_ +#define _SLACK_REQUEST_CHAT_POSTMESSAGE_H_ + +struct t_slack_request *slack_request_chat_postmessage( + struct t_slack_workspace *workspace, + const char *token, const char *channel, + const char *text); + +#endif /*SLACK_REQUEST_CHAT_POSTMESSAGE_H*/ diff --git a/slack-api.c b/slack-api.c index 4492d18..2314951 100644 --- a/slack-api.c +++ b/slack-api.c @@ -18,7 +18,7 @@ struct stringcase json_object *message); }; -struct stringcase cases[] = +static struct stringcase cases[] = { { "hello", &slack_api_hello } , { "error", &slack_api_error } , { "message", &slack_api_message } @@ -33,6 +33,8 @@ void slack_api_init() { size_t case_count = sizeof(cases) / sizeof(cases[0]); qsort(cases, case_count, sizeof(struct stringcase), stringcase_cmp); + + slack_api_message_init(); } static int callback_ws(struct lws* wsi, enum lws_callback_reasons reason, diff --git a/slack-buffer.c b/slack-buffer.c index 04ff85e..d50816d 100644 --- a/slack-buffer.c +++ b/slack-buffer.c @@ -1,8 +1,8 @@ #include "weechat-plugin.h" #include "slack.h" -#include "slack-buffer.h" #include "slack-workspace.h" #include "slack-channel.h" +#include "slack-buffer.h" void slack_buffer_get_workspace_and_channel(struct t_gui_buffer *buffer, struct t_slack_workspace **workspace, diff --git a/slack-buffer.h b/slack-buffer.h index 7410ab2..649c813 100644 --- a/slack-buffer.h +++ b/slack-buffer.h @@ -1,6 +1,10 @@ #ifndef _SLACK_BUFFER_H_ #define _SLACK_BUFFER_H_ +void slack_buffer_get_workspace_and_channel(struct t_gui_buffer *buffer, + struct t_slack_workspace **workspace, + struct t_slack_channel **channel); + int slack_buffer_nickcmp_cb(const void *pointer, void *data, struct t_gui_buffer *buffer, const char *nick1, diff --git a/slack-input.c b/slack-input.c index ec36d5d..1b06398 100644 --- a/slack-input.c +++ b/slack-input.c @@ -1,10 +1,63 @@ +#include +#include + #include "weechat-plugin.h" +#include "slack.h" +#include "slack-workspace.h" +#include "slack-channel.h" +#include "slack-buffer.h" +#include "slack-request.h" +#include "slack-message.h" #include "slack-input.h" +#include "request/slack-request-chat-postmessage.h" int slack_input_data(struct t_gui_buffer *buffer, const char *input_data) { - (void) buffer; - (void) input_data; + struct t_slack_workspace *workspace = NULL; + struct t_slack_channel *channel = NULL; + struct t_slack_request *request; + char *text; + + slack_buffer_get_workspace_and_channel(buffer, &workspace, &channel); + + if (!workspace) + return WEECHAT_RC_ERROR; + + if (channel) + { + if (!workspace->is_connected) + { + weechat_printf(buffer, + _("%s%s: you are not connected to server"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return WEECHAT_RC_OK; + } + + text = malloc(SLACK_MESSAGE_MAX_LENGTH); + if (!text) + { + weechat_printf(buffer, + _("%s%s: error allocating string"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return WEECHAT_RC_ERROR; + } + lws_urlencode(text, input_data, SLACK_MESSAGE_MAX_LENGTH); + + request = slack_request_chat_postmessage(workspace, + weechat_config_string( + workspace->options[SLACK_WORKSPACE_OPTION_TOKEN]), + channel->id, text); + if (request) + slack_workspace_register_request(workspace, request); + + free(text); + } + else + { + weechat_printf(buffer, + _("%s%s: this buffer is not a channel!"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + } return WEECHAT_RC_OK; } diff --git a/slack-message.c b/slack-message.c new file mode 100644 index 0000000..4aa6ef1 --- /dev/null +++ b/slack-message.c @@ -0,0 +1,139 @@ +#include +#include +#include + +#include "../weechat-plugin.h" +#include "../slack.h" +#include "../slack-workspace.h" +#include "../slack-channel.h" +#include "../slack-user.h" +#include "../slack-message.h" + +static const char format_regex[] = "<(.*?)>"; +static const size_t max_groups = 2; + +char *slack_message_translate_code(struct t_slack_workspace *workspace, + const char *code) +{ + struct t_slack_channel *channel; + struct t_slack_user *user; + char *command; + + switch (code[0]) + { + case '#': /* channel */ + channel = slack_channel_search(workspace, code+1); + if (channel) + return strdup(channel->name); + else + return strdup(code); + case '@': /* user */ + user = slack_user_search(workspace, code+1); + if (user) + return strdup(user->profile.display_name); + else + return strdup(code); + case '!': /* special */ + command = strdup(code); + command[0] = '@'; + return command; + default: /* url */ + return strdup(code); + } +} + +char *slack_message_decode(struct t_slack_workspace *workspace, + const char *text) +{ + int rc; + regex_t reg; + regmatch_t groups[max_groups]; + char msgbuf[100]; + char *decoded_text, *pos; + const char *cursor; + size_t offset; + + if ((rc = regcomp(®, format_regex, REG_EXTENDED))) + { + regerror(rc, ®, msgbuf, sizeof(msgbuf)); + weechat_printf( + workspace->buffer, + _("%s%s: error compiling message formatting regex: %s"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, + msgbuf); + return strdup(text); + } + + decoded_text = malloc(SLACK_MESSAGE_MAX_LENGTH); + if (!decoded_text) + { + regfree(®); + weechat_printf( + workspace->buffer, + _("%s%s: error allocating space for message"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return strdup(text); + } + pos = decoded_text; + pos[0] = '\0'; + + for (cursor = text; regexec(®, cursor, max_groups, groups, 0) == 0; cursor += offset) + { + offset = groups[0].rm_eo; + + char *copy = strdup(cursor); + if (!copy) + { + regfree(®); + weechat_printf( + workspace->buffer, + _("%s%s: error allocating space for message"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return strdup(text); + } + copy[groups[1].rm_eo] = '\0'; + + char *match = strdup(copy + groups[1].rm_so); + if (!match) + { + free(copy); + regfree(®); + weechat_printf( + workspace->buffer, + _("%s%s: error allocating space for message"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return strdup(text); + } + copy[groups[0].rm_so] = '\0'; + + char *prematch = strdup(copy); + if (!prematch) + { + free(match); + free(copy); + regfree(®); + weechat_printf( + workspace->buffer, + _("%s%s: error allocating space for message"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return strdup(text); + } + free(copy); + + pos = strncat(decoded_text, prematch, + SLACK_MESSAGE_MAX_LENGTH - strlen(decoded_text) - 1); + free(prematch); + + char *replacement = slack_message_translate_code(workspace, match); + free(match); + + pos = strncat(decoded_text, replacement, + SLACK_MESSAGE_MAX_LENGTH - strlen(decoded_text) - 1); + free(replacement); + } + pos = strncat(decoded_text, cursor, + SLACK_MESSAGE_MAX_LENGTH - strlen(decoded_text) - 1); + + regfree(®); + return decoded_text; +} diff --git a/slack-message.h b/slack-message.h new file mode 100644 index 0000000..baca30f --- /dev/null +++ b/slack-message.h @@ -0,0 +1,9 @@ +#ifndef _SLACK_MESSAGE_H_ +#define _SLACK_MESSAGE_H_ + +#define SLACK_MESSAGE_MAX_LENGTH 40000 + +char *slack_message_decode(struct t_slack_workspace *workspace, + const char *text); + +#endif /*SLACK_MESSAGE_H*/ diff --git a/slack-user.c b/slack-user.c index b78184c..3b0d0fd 100644 --- a/slack-user.c +++ b/slack-user.c @@ -1,5 +1,6 @@ #include #include +#include #include "weechat-plugin.h" #include "slack.h" @@ -7,6 +8,25 @@ #include "slack-user.h" #include "slack-channel.h" +const char *slack_user_get_colour(struct t_slack_user *user) +{ + return weechat_info_get("nick_color", user->profile.display_name); +} + +const char *slack_user_as_prefix(struct t_slack_workspace *workspace, + struct t_slack_user *user) +{ + static char result[256]; + + (void) workspace; + + snprintf(result, sizeof(result), "%s%s\t", + slack_user_get_colour(user), + user->profile.display_name); + + return result; +} + struct t_slack_user *slack_user_search(struct t_slack_workspace *workspace, const char *id) { diff --git a/slack-user.h b/slack-user.h index ecf6944..1cd18e2 100644 --- a/slack-user.h +++ b/slack-user.h @@ -45,6 +45,9 @@ struct t_slack_user struct t_slack_user *next_user; }; +const char *slack_user_as_prefix(struct t_slack_workspace *workspace, + struct t_slack_user *user); + struct t_slack_user *slack_user_search(struct t_slack_workspace *workspace, const char *id); diff --git a/slack-workspace.c b/slack-workspace.c index 8438fba..2d8fc6d 100644 --- a/slack-workspace.c +++ b/slack-workspace.c @@ -7,7 +7,6 @@ #include "weechat-plugin.h" #include "slack.h" -#include "slack-buffer.h" #include "slack-config.h" #include "slack-input.h" #include "slack-workspace.h" @@ -15,6 +14,7 @@ #include "slack-request.h" #include "slack-user.h" #include "slack-channel.h" +#include "slack-buffer.h" struct t_slack_workspace *slack_workspaces = NULL; struct t_slack_workspace *last_slack_workspace = NULL; diff --git a/slack.c b/slack.c index c1cee92..4b59065 100644 --- a/slack.c +++ b/slack.c @@ -56,7 +56,7 @@ int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char *argv[]) slack_api_init(); - slack_hook_timer = weechat_hook_timer(1 * 1000, 0, 0, + slack_hook_timer = weechat_hook_timer(0.1 * 1000, 0, 0, &slack_workspace_timer_cb, NULL, NULL);