diff --git a/Makefile b/Makefile index 4568e53..9ea5d8e 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,9 @@ ifdef DEBUG - CC=clang - CXX=g++ - DBGCFLAGS=-fsanitize=address -fsanitize=leak - DBGLDFLAGS=-static-libasan -static-liblsan + DBGCFLAGS=-fsanitize=address -fsanitize=leak -fsanitize=undefined + DBGLDFLAGS=-static-libasan -static-liblsan -static-libubsan endif RM=rm -f +FIND=find CFLAGS+=$(DBGCFLAGS) -fno-omit-frame-pointer -fPIC -std=gnu99 -g -Wall -Wextra -Werror-implicit-function-declaration -Wno-missing-field-initializers -Ilibwebsockets/include -Ijson-c LDFLAGS+=-shared -g $(DBGCFLAGS) $(DBGLDFLAGS) LDLIBS=-lgnutls @@ -40,13 +39,14 @@ SRCS=slack.c \ request/slack-request-chat-postmessage.c \ request/slack-request-channels-list.c \ request/slack-request-conversations-members.c \ + request/slack-request-emoji-list.c \ request/slack-request-users-list.c OBJS=$(subst .c,.o,$(SRCS)) libwebsockets/lib/libwebsockets.a json-c/libjson-c.a all: libwebsockets/lib/libwebsockets.a json-c/libjson-c.a weechat-slack weechat-slack: $(OBJS) - $(CXX) $(LDFLAGS) -o slack.so $(OBJS) $(LDLIBS) + $(CC) $(LDFLAGS) -o slack.so $(OBJS) $(LDLIBS) ifeq ($(shell which python),) slack-emoji.inc: slack-emoji.pl @@ -70,7 +70,10 @@ depend: .depend .depend: libwebsockets/lib/libwebsockets.a json-c/libjson-c.a $(SRCS) $(RM) ./.depend - $(CC) $(CFLAGS) -MM $^>>./.depend; + $(CC) $(CFLAGS) -MM $^>>./.depend + +tidy: + $(FIND) . -name "*.o" -delete clean: $(RM) $(OBJS) diff --git a/README.org b/README.org index 333fd24..a83f0f6 100644 --- a/README.org +++ b/README.org @@ -77,11 +77,11 @@ - [ ] Implement handling api message =message.message_replied= - [ ] Implement sending websocket =typing= message ** TODO [#B] Implement completion engine (milestone v0.3) - - [ ] Tab completion for slack emoji (see [[http://github.com/bqv/weechat-slack/issues/3][#3]]) + - [X] +Tab completion for slack emoji (see [[http://github.com/bqv/weechat-slack/issues/3][#3]])+ - [X] +Support Slack Emoji+ - - [ ] Support Custom Emoji + - [X] +Support Custom Emoji+ - [ ] Tab completion for display/user names (see [[http://github.com/bqv/weechat-slack/issues/1][#1]]) - - [ ] Sort nick-completion by recent (see [[http://github.com/bqv/weechat-slack/issues/4][#4]]) + - [ ] Sort nick-completion by recent speakers (see [[http://github.com/bqv/weechat-slack/issues/4][#4]]) ** TODO [#B] Implement websocket ping and pong (milestone v0.4) - [ ] Add ping timer and pong handler (see [[http://github.com/bqv/weechat-slack/issues/9][#9]]) ** TODO [#C] Implement remaining api endpoints and events (milestone v0.5) @@ -108,7 +108,7 @@ As there is no C library for Slack at the time of writing, this project implements the APIs from scratch, and as such one could butcher this repository - to create a minimal Slack C library. + to create a minimal Slack C library. Up to you. * License diff --git a/api/slack-api-hello.c b/api/slack-api-hello.c index 87fa406..611cfc6 100644 --- a/api/slack-api-hello.c +++ b/api/slack-api-hello.c @@ -11,6 +11,7 @@ #include "slack-api-hello.h" #include "../request/slack-request-channels-list.h" #include "../request/slack-request-users-list.h" +#include "../request/slack-request-emoji-list.h" int slack_api_hello_handle(struct t_slack_workspace *workspace) { @@ -35,6 +36,12 @@ int slack_api_hello_handle(struct t_slack_workspace *workspace) if (request) slack_workspace_register_request(workspace, request); + request = slack_request_emoji_list(workspace, + weechat_config_string( + workspace->options[SLACK_WORKSPACE_OPTION_TOKEN])); + if (request) + slack_workspace_register_request(workspace, request); + return 1; } diff --git a/request/slack-request-emoji-list.c b/request/slack-request-emoji-list.c new file mode 100644 index 0000000..abc303b --- /dev/null +++ b/request/slack-request-emoji-list.c @@ -0,0 +1,287 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, version 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include + +#include "../weechat-plugin.h" +#include "../slack.h" +#include "../slack-workspace.h" +#include "../slack-request.h" +#include "../slack-channel.h" +#include "../request/slack-request-emoji-list.h" + +static const char *const endpoint = "/api/emoji.list?" + "token=%s"; + +static inline int json_valid(json_object *object, struct t_slack_workspace *workspace) +{ + if (!object) + { + weechat_printf( + workspace->buffer, + _("%s%s: error retrieving emoji: 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 emoji... (%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, *emoji; + 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)) + { + emoji = json_object_object_get(response, "emoji"); + if (!json_valid(emoji, request->workspace)) + { + json_object_put(response); + free(json_string); + return 0; + } + + json_object_object_foreach(emoji, key, val) + { + if (!json_valid(val, request->workspace)) + { + continue; + } + + slack_workspace_add_emoji( + request->workspace, + key, json_object_get_string(val)); + } + } + 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 emoji: %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_emoji_list( + struct t_slack_workspace *workspace, + const char *token) +{ + 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) + 1; + request->uri = malloc(urilen); + snprintf(request->uri, urilen, endpoint, token); + + 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-emoji-list.h b/request/slack-request-emoji-list.h new file mode 100644 index 0000000..19101d1 --- /dev/null +++ b/request/slack-request-emoji-list.h @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, version 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef _SLACK_REQUEST_EMOJI_LIST_H_ +#define _SLACK_REQUEST_EMOJI_LIST_H_ + +struct t_slack_request *slack_request_emoji_list( + struct t_slack_workspace *workspace, + const char *token); + +#endif /*SLACK_REQUEST_EMOJI_LIST_H*/ diff --git a/request/slack-request-users-list.c b/request/slack-request-users-list.c index 62d5463..a8ec9af 100644 --- a/request/slack-request-users-list.c +++ b/request/slack-request-users-list.c @@ -220,9 +220,6 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, 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)) diff --git a/slack-emoji.c b/slack-emoji.c index 7ea1392..3ec6802 100644 --- a/slack-emoji.c +++ b/slack-emoji.c @@ -102,6 +102,7 @@ int slack_emoji_complete_by_name_cb(const void *pointer, void *data, struct t_gui_buffer *buffer, struct t_gui_completion *completion) { + struct t_slack_workspace_emoji *ptr_emoji; struct t_slack_workspace *workspace; struct t_slack_channel *channel; @@ -109,15 +110,25 @@ int slack_emoji_complete_by_name_cb(const void *pointer, void *data, (void) data; (void) completion_item; + workspace = NULL; slack_buffer_get_workspace_and_channel(buffer, &workspace, &channel); size_t i, emoji_count = sizeof(slack_emoji_by_name) / sizeof(struct t_slack_emoji_by_name); + + if (workspace) + { + for (ptr_emoji = workspace->emoji; ptr_emoji; + ptr_emoji = ptr_emoji->next_emoji) + weechat_hook_completion_list_add(completion, + ptr_emoji->name, + 0, WEECHAT_LIST_POS_END); - for (i = 0; i < emoji_count; i++) - weechat_hook_completion_list_add(completion, - slack_emoji_by_name[i].name, - 0, WEECHAT_LIST_POS_END); + for (i = 0; i < emoji_count; i++) + weechat_hook_completion_list_add(completion, + slack_emoji_by_name[i].name, + 0, WEECHAT_LIST_POS_END); + } return WEECHAT_RC_OK; } diff --git a/slack-workspace.c b/slack-workspace.c index b820d1d..23228fe 100644 --- a/slack-workspace.c +++ b/slack-workspace.c @@ -388,6 +388,8 @@ struct t_slack_workspace *slack_workspace_alloc(const char *domain) new_workspace->last_user = NULL; new_workspace->channels = NULL; new_workspace->last_channel = NULL; + new_workspace->emoji = NULL; + new_workspace->last_emoji = NULL; /* create options with null value */ for (i = 0; i < SLACK_WORKSPACE_NUM_OPTIONS; i++) @@ -911,3 +913,60 @@ void slack_workspace_register_request(struct t_slack_workspace *workspace, workspace->requests = request; workspace->last_request = request; } + +struct t_slack_workspace_emoji *slack_workspace_emoji_search( + struct t_slack_workspace *workspace, + const char *name) +{ + struct t_slack_workspace_emoji *ptr_emoji; + + if (!workspace || !name) + return NULL; + + for (ptr_emoji = workspace->emoji; ptr_emoji; + ptr_emoji = ptr_emoji->next_emoji) + { + if (weechat_strcasecmp(ptr_emoji->name, name) == 0) + return ptr_emoji; + } + + return NULL; +} + +struct t_slack_workspace_emoji *slack_workspace_add_emoji( + struct t_slack_workspace *workspace, + const char *name, const char *url) +{ + struct t_slack_workspace_emoji *ptr_emoji, *new_emoji; + char shortname[SLACK_WORKSPACE_EMOJI_SHORTNAME_MAX_LEN + 1]; + + (void) url; + + if (!workspace || !name || !name[0]) + return NULL; + + snprintf(shortname, SLACK_WORKSPACE_EMOJI_SHORTNAME_MAX_LEN + 1, + ":%s:", name); + + ptr_emoji = slack_workspace_emoji_search(workspace, shortname); + if (ptr_emoji) + { + return ptr_emoji; + } + + if ((new_emoji = malloc(sizeof(*new_emoji))) == NULL) + return NULL; + + new_emoji->name = strdup(shortname); + new_emoji->url = strdup(url); + + new_emoji->prev_emoji = workspace->last_emoji; + new_emoji->next_emoji = NULL; + if (workspace->last_emoji) + (workspace->last_emoji)->next_emoji = new_emoji; + else + workspace->emoji = new_emoji; + workspace->last_emoji = new_emoji; + + return new_emoji; +} diff --git a/slack-workspace.h b/slack-workspace.h index d858b08..030b4ff 100644 --- a/slack-workspace.h +++ b/slack-workspace.h @@ -5,9 +5,20 @@ #ifndef _SLACK_WORKSPACE_H_ #define _SLACK_WORKSPACE_H_ +#define SLACK_WORKSPACE_EMOJI_SHORTNAME_MAX_LEN 1 + 100 + 1 + extern struct t_slack_workspace *slack_workspaces; extern struct t_slack_workspace *last_slack_workspace; +struct t_slack_workspace_emoji +{ + char *name; + char *url; + + struct t_slack_workspace_emoji *prev_emoji; + struct t_slack_workspace_emoji *next_emoji; +}; + enum t_slack_workspace_option { SLACK_WORKSPACE_OPTION_TOKEN, @@ -51,6 +62,8 @@ struct t_slack_workspace struct t_slack_user *last_user; struct t_slack_channel *channels; struct t_slack_channel *last_channel; + struct t_slack_workspace_emoji *emoji; + struct t_slack_workspace_emoji *last_emoji; struct t_slack_workspace *prev_workspace; struct t_slack_workspace *next_workspace; }; @@ -72,5 +85,11 @@ int slack_workspace_connect(struct t_slack_workspace *workspace); int slack_workspace_timer_cb(const void *pointer, void *data, int remaining_calls); void slack_workspace_register_request(struct t_slack_workspace *workspace, struct t_slack_request *request); +struct t_slack_workspace_emoji *slack_workspace_emoji_search( + struct t_slack_workspace *workspace, + const char *name); +struct t_slack_workspace_emoji *slack_workspace_add_emoji( + struct t_slack_workspace *workspace, + const char *name, const char *url); #endif /*SLACK_WORKSPACE_H*/