From 47a8e86a34ff4e775e393cd8929ea1bcbb99ae23 Mon Sep 17 00:00:00 2001 From: Tony Olagbaiye Date: Tue, 1 May 2018 11:23:37 +0100 Subject: [PATCH] Server buffer creation --- Makefile | 10 +- libwebsockets | 2 +- slack-api.c | 158 +++++++++++ slack-api.h | 6 + slack-buffer.c | 73 +++++ slack-buffer.h | 7 + slack-command.c | 200 ++++++++++++-- slack-config.c | 34 ++- slack-input.c | 20 ++ slack-input.h | 8 + slack-oauth.c | 33 ++- slack-teaminfo.c | 342 +++++++++++++++++++++++ slack-teaminfo.h | 16 ++ slack-workspace.c | 671 +++++++++++++++++++++++++++++++++++++++++++++- slack-workspace.h | 34 ++- slack.c | 34 +++ 16 files changed, 1591 insertions(+), 57 deletions(-) create mode 100644 slack-api.c create mode 100644 slack-api.h create mode 100644 slack-buffer.c create mode 100644 slack-buffer.h create mode 100644 slack-input.c create mode 100644 slack-input.h create mode 100644 slack-teaminfo.c create mode 100644 slack-teaminfo.h diff --git a/Makefile b/Makefile index 0bde5c4..fda87ab 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,28 @@ CC=clang CXX=clang++ RM=rm -f -CFLAGS=-fPIC -std=gnu99 -g -Wall -Wextra -Werror-implicit-function-declaration -I libwebsockets/include -I json-c +CFLAGS=-fPIC -std=gnu99 -g -Wall -Wextra -Werror-implicit-function-declaration -Wno-missing-field-initializers -I libwebsockets/include -I json-c LDFLAGS=-shared -g LDLIBS=-lssl SRCS=slack.c \ + slack-api.c \ + slack-buffer.c \ slack-config.c \ slack-command.c \ + slack-input.c \ slack-oauth.c \ + slack-teaminfo.c \ slack-workspace.c OBJS=$(subst .c,.o,$(SRCS)) libwebsockets/lib/libwebsockets.a json-c/libjson-c.a -all: weechat-slack +all: libwebsockets/lib/libwebsockets.a json-c/libjson-c.a weechat-slack weechat-slack: $(OBJS) $(CC) $(LDFLAGS) -o slack.so $(OBJS) $(LDLIBS) libwebsockets/lib/libwebsockets.a: - cd libwebsockets && cmake -DLWS_STATIC_PIC=ON -DLWS_WITH_SHARED=OFF -DLWS_WITHOUT_TESTAPPS=ON -DLWS_WITH_LIBEV=OFF -DLWS_WITH_LIBUV=OFF -DLWS_WITH_LIBEVENT=OFF . + cd libwebsockets && cmake -DLWS_STATIC_PIC=ON -DLWS_WITH_SHARED=OFF -DLWS_WITHOUT_TESTAPPS=ON -DLWS_WITH_LIBEV=OFF -DLWS_WITH_LIBUV=OFF -DLWS_WITH_LIBEVENT=OFF -DCMAKE_BUILD_TYPE=DEBUG . $(MAKE) -C libwebsockets json-c/libjson-c.a: diff --git a/libwebsockets b/libwebsockets index 91a47f4..f1c56bc 160000 --- a/libwebsockets +++ b/libwebsockets @@ -1 +1 @@ -Subproject commit 91a47f4fab4d74f49a341ce22954a563f6544446 +Subproject commit f1c56bc233a5f05c01c93a5c250a31b4d309ecac diff --git a/slack-api.c b/slack-api.c new file mode 100644 index 0000000..0cd3b78 --- /dev/null +++ b/slack-api.c @@ -0,0 +1,158 @@ +#include +#include + +#include "weechat-plugin.h" +#include "slack.h" +#include "slack-workspace.h" +#include "slack-api.h" + +static int callback_ws(struct lws* wsi, enum lws_callback_reasons reason, + void *user, void* in, size_t len) +{ + struct t_slack_workspace *workspace = (struct t_slack_workspace *)user; + + (void) wsi; + + switch (reason) + { + /* because we are protocols[0] ... */ + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + weechat_printf( + workspace->buffer, + _("%s%s: error connecting to slack: %s"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, + in ? (char *)in : "(null)"); + workspace->client_wsi = NULL; + break; + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + weechat_printf( + workspace->buffer, + _("%s%s: waiting for hello..."), + weechat_prefix("network"), SLACK_PLUGIN_NAME); + break; + + /* chunks of chunked content, with header removed */ + case LWS_CALLBACK_CLIENT_RECEIVE: + weechat_printf( + workspace->buffer, + _("%s%s: received data: %s"), + weechat_prefix("network"), SLACK_PLUGIN_NAME, + (const char *)in); + { + 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 (workspace->json_chunks) + { + for (last_chunk = workspace->json_chunks; last_chunk->next; + last_chunk = last_chunk->next); + last_chunk->next = new_chunk; + } + else + { + workspace->json_chunks = new_chunk; + } + } + return 0; /* don't passthru */ + + case LWS_CALLBACK_CLIENT_WRITEABLE: + weechat_printf( + workspace->buffer, + _("%s%s: websocket is writeable"), + weechat_prefix("network"), SLACK_PLUGIN_NAME); + break; + + case LWS_CALLBACK_CLOSED: + workspace->client_wsi = NULL; + workspace->disconnected = 1; + /* start reconnect timer */ + break; + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +static const struct lws_protocols protocols[] = { + { + "default", + callback_ws, + 0, + 0, + }, + { NULL, NULL, 0, 0 } +}; + +void slack_api_connect(struct t_slack_workspace *workspace) +{ + struct lws_context_creation_info ctxinfo; + struct lws_client_connect_info ccinfo; + const char *url_protocol, *url_path; + char path[512]; + + memset(&ctxinfo, 0, sizeof(ctxinfo)); + memset(&ccinfo, 0, sizeof(ccinfo)); + + ccinfo.port = 443; + + if (lws_parse_uri(workspace->ws_url, + &url_protocol, &ccinfo.address, + &ccinfo.port, &url_path)) + { + weechat_printf( + workspace->buffer, + _("%s%s: error connecting to slack: bad websocket uri"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return; + } + + path[0] = '/'; + strncpy(path + 1, url_path, sizeof(path) - 2); + path[sizeof(path) - 1] = '\0'; + + ccinfo.path = path; + + ctxinfo.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + ctxinfo.port = CONTEXT_PORT_NO_LISTEN; + ctxinfo.protocols = protocols; + ctxinfo.uid = -1; + ctxinfo.gid = -1; + + workspace->context = lws_create_context(&ctxinfo); + if (!workspace->context) + { + weechat_printf( + workspace->buffer, + _("%s%s: error connecting to slack: lws init failed"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return; + } + else + { + weechat_printf( + workspace->buffer, + _("%s%s: connecting to %s://%s:%d%s"), + weechat_prefix("network"), SLACK_PLUGIN_NAME, + url_protocol, ccinfo.address, ccinfo.port, path); + } + + ccinfo.context = workspace->context; + ccinfo.ssl_connection = LCCSCF_USE_SSL; + ccinfo.host = ccinfo.address; + ccinfo.origin = ccinfo.address; + ccinfo.ietf_version_or_minus_one = -1; + ccinfo.protocol = protocols[0].name; + ccinfo.pwsi = &workspace->client_wsi; + ccinfo.userdata = workspace; + + lws_client_connect_via_info(&ccinfo); +} diff --git a/slack-api.h b/slack-api.h new file mode 100644 index 0000000..6a0281b --- /dev/null +++ b/slack-api.h @@ -0,0 +1,6 @@ +#ifndef _SLACK_API_H_ +#define _SLACK_API_H_ + +void slack_api_connect(struct t_slack_workspace *workspace); + +#endif /*SLACK_API_H*/ diff --git a/slack-buffer.c b/slack-buffer.c new file mode 100644 index 0000000..8fceb3d --- /dev/null +++ b/slack-buffer.c @@ -0,0 +1,73 @@ +#include "weechat-plugin.h" +#include "slack.h" +#include "slack-buffer.h" +#include "slack-workspace.h" + +void slack_buffer_get_workspace_and_channel(struct t_gui_buffer *buffer, + struct t_slack_workspace **workspace)//, + //struct t_slack_channel **channel) +{ + struct t_slack_workspace *ptr_workspace; + //struct t_slack_channel *ptr_channel; + + if (!buffer) + return; + + /* look for a workspace or channel using this buffer */ + for (ptr_workspace = slack_workspaces; ptr_workspace; + ptr_workspace = ptr_workspace->next_workspace) + { + if (ptr_workspace->buffer == buffer) + { + if (workspace) + *workspace = ptr_workspace; + return; + } + + /* + for (ptr_channel = ptr_workspace->channels; ptr_channel; + ptr_channel = ptr_channel->next_channel) + { + if (ptr_channel->buffer == buffer) + { + if (workspace) + *workspace = ptr_workspace; + if (channel) + *channel = ptr_channel; + return; + } + } + */ + } + + /* no workspace or channel found */ +} + +int slack_buffer_close_cb(const void *pointer, void *data, + struct t_gui_buffer *buffer) +{ + struct t_weechat_plugin *buffer_plugin = NULL; + struct t_slack_workspace *ptr_workspace = NULL; + + buffer_plugin = weechat_buffer_get_pointer(buffer, "plugin"); + if (buffer_plugin == weechat_slack_plugin) + slack_buffer_get_workspace_and_channel(buffer, + &ptr_workspace);//, &ptr_channel); + + (void) pointer; + (void) data; + (void) buffer; + + if (ptr_workspace) + { + if (!ptr_workspace->disconnected) + { + //slack_command_quit_workspace(ptr_workspace, NULL); + slack_workspace_disconnect(ptr_workspace, 0, 0); + } + + ptr_workspace->buffer = NULL; + } + + return WEECHAT_RC_OK; +} diff --git a/slack-buffer.h b/slack-buffer.h new file mode 100644 index 0000000..8ba6a03 --- /dev/null +++ b/slack-buffer.h @@ -0,0 +1,7 @@ +#ifndef _SLACK_BUFFER_H_ +#define _SLACK_BUFFER_H_ + +int slack_buffer_close_cb(const void *pointer, void *data, + struct t_gui_buffer *buffer); + +#endif /*SLACK_BUFFER_H*/ diff --git a/slack-command.c b/slack-command.c index 9c940da..0d7f4b8 100644 --- a/slack-command.c +++ b/slack-command.c @@ -5,6 +5,7 @@ #include "slack.h" #include "slack-command.h" #include "slack-oauth.h" +#include "slack-teaminfo.h" #include "slack-workspace.h" void slack_command_display_workspace(struct t_slack_workspace *workspace) @@ -17,10 +18,15 @@ void slack_command_display_workspace(struct t_slack_workspace *workspace) num_pv = 0;//slack_workspace_get_pv_count(workspace); weechat_printf( NULL, - " %s %s%s %s[%s%s%s]%s, %d %s, %d pv", + " %s %s%s%s.slack.com %s(%s%s%s) [%s%s%s]%s, %d %s, %d pv", (workspace->is_connected) ? "*" : " ", - weechat_color("chat_workspace"), - workspace->name, + weechat_color("chat_server"), + workspace->domain, + weechat_color("reset"), + weechat_color("chat_delimiters"), + weechat_color("chat_server"), + (workspace->name) ? + workspace->name : "???", weechat_color("chat_delimiters"), weechat_color("reset"), (workspace->is_connected) ? @@ -35,9 +41,15 @@ void slack_command_display_workspace(struct t_slack_workspace *workspace) { weechat_printf( NULL, - " %s%s%s", - weechat_color("chat_workspace"), - workspace->name, + " %s%s%s.slack.com %s(%s%s%s)%s", + weechat_color("chat_server"), + workspace->domain, + weechat_color("reset"), + weechat_color("chat_delimiters"), + weechat_color("chat_server"), + (workspace->name) ? + workspace->name : "???", + weechat_color("chat_delimiters"), weechat_color("reset")); } } @@ -94,8 +106,56 @@ void slack_command_workspace_list(int argc, char **argv) } } -void slack_command_add_workspace(char *token) +void slack_command_add_workspace(struct t_slack_teaminfo *slack_teaminfo) { + struct t_slack_workspace *workspace; + + workspace = slack_workspace_casesearch(slack_teaminfo->domain); + if (workspace) + { + weechat_printf( + NULL, + _("%s%s: workspace \"%s\" already exists, can't add it!"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, + slack_teaminfo->domain); + return; + } + + workspace = slack_workspace_alloc(slack_teaminfo->domain); + if (!workspace) + { + weechat_printf( + NULL, + _("%s%s: unable to add workspace"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return; + } + + workspace->id = strdup(slack_teaminfo->id); + workspace->name = strdup(slack_teaminfo->name); + weechat_config_option_set(workspace->options[SLACK_WORKSPACE_OPTION_TOKEN], + slack_teaminfo->token, 1); + + weechat_printf ( + NULL, + _("%s: workspace %s%s%s.slack.com %s(%s%s%s)%s added"), + SLACK_PLUGIN_NAME, + weechat_color("chat_server"), + workspace->domain, + weechat_color("reset"), + weechat_color("chat_delimiters"), + weechat_color("chat_server"), + workspace->name, + weechat_color("chat_delimiters"), + weechat_color("reset")); + + free_teaminfo(slack_teaminfo); +} + +void slack_command_fetch_workspace(char *token) +{ + slack_teaminfo_fetch(token, &slack_command_add_workspace); + free(token); } @@ -107,13 +167,13 @@ void slack_command_workspace_register(int argc, char **argv) { code = argv[2]; - if (weechat_strncasecmp("xoxp", code, 4) == 0) + if (strncmp("xoxp", code, 4) == 0) { - slack_command_add_workspace(strdup(code)); + slack_command_fetch_workspace(strdup(code)); } else { - slack_oauth_request_token(code, &slack_command_add_workspace); + slack_oauth_request_token(code, &slack_command_fetch_workspace); } } else @@ -121,7 +181,7 @@ void slack_command_workspace_register(int argc, char **argv) weechat_printf(NULL, _("\n#### Retrieving a Slack token via OAUTH ####\n" "1) Paste this into a browser: https://slack.com/oauth/authorize?client_id=%s&scope=client\n" - "2) Select the team you wish to access from wee-slack in your browser.\n" + "2) Select the team you wish to access from weechat in your browser.\n" "3) Click \"Authorize\" in the browser **IMPORTANT: the redirect will fail, this is expected**\n" "4) Copy the \"code\" portion of the URL to your clipboard\n" "5) Return to weechat and run `/slack register [code]`\n"), @@ -129,8 +189,109 @@ void slack_command_workspace_register(int argc, char **argv) } } +int slack_command_connect_workspace(struct t_slack_workspace *workspace) +{ + if (!workspace) + return 0; + + if (workspace->is_connected) + { + weechat_printf( + NULL, + _("%s%s: already connected to workspace \"%s\"!"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, + workspace->domain); + } + + slack_workspace_connect(workspace); + + return 1; +} + +int slack_command_workspace_connect(int argc, char **argv) +{ + int i, nb_connect, connect_ok; + struct t_slack_workspace *ptr_workspace; + + (void) argc; + (void) argv; + + connect_ok = 1; + + nb_connect = 0; + for (i = 2; i < argc; i++) + { + nb_connect++; + ptr_workspace = slack_workspace_search(argv[i]); + if (ptr_workspace) + { + if (!slack_command_connect_workspace(ptr_workspace)) + { + connect_ok = 0; + } + } + else + { + weechat_printf( + NULL, + _("%s%s: workspace not found \"%s\" " + "(register first with: /slack register)"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, + argv[i]); + } + } + + return (connect_ok) ? WEECHAT_RC_OK : WEECHAT_RC_ERROR; +} + void slack_command_workspace_delete(int argc, char **argv) { + struct t_slack_workspace *workspace; + char *workspace_domain; + + if (argc < 3) + { + weechat_printf( + NULL, + _("%sToo few arguments for command\"%s %s\" " + "(help on command: /help %s)"), + weechat_prefix("error"), + argv[0], argv[1], argv[0] + 1); + return; + } + + workspace = slack_workspace_search(argv[2]); + if (!workspace) + { + weechat_printf( + NULL, + _("%s%s: workspace \"%s\" not found for \"%s\" command"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, + argv[2], "slack delete"); + return; + } + if (workspace->is_connected) + { + weechat_printf( + NULL, + _("%s%s: you cannot delete workspace \"%s\" because you" + "are connected. Try \"/slack disconnect %s\" first."), + weechat_prefix("error"), SLACK_PLUGIN_NAME, + argv[2], argv[2]); + return; + } + + workspace_domain = strdup(workspace->domain); + slack_workspace_free(workspace); + weechat_printf ( + NULL, + _("%s: workspace %s%s%s has been deleted"), + SLACK_PLUGIN_NAME, + weechat_color("chat_server"), + (workspace_domain) ? workspace_domain : "???", + weechat_color("reset")); + if (workspace_domain) + free(workspace_domain); } int slack_command_slack(const void *pointer, void *data, @@ -142,17 +303,23 @@ int slack_command_slack(const void *pointer, void *data, (void) data; (void) buffer; + if (argc <= 1 || weechat_strcasecmp(argv[1], "list") == 0) + { + slack_command_workspace_list(argc, argv); + return WEECHAT_RC_OK; + } + if (argc > 1) { - if (weechat_strcasecmp(argv[1], "list") == 0) + if (weechat_strcasecmp(argv[1], "register") == 0) { - slack_command_workspace_list(argc, argv); + slack_command_workspace_register(argc, argv); return WEECHAT_RC_OK; } - if (weechat_strcasecmp(argv[1], "register") == 0) + if (weechat_strcasecmp(argv[1], "connect") == 0) { - slack_command_workspace_register(argc, argv); + slack_command_workspace_connect(argc, argv); return WEECHAT_RC_OK; } @@ -175,12 +342,15 @@ void slack_command_init() N_("slack control"), N_("list" " || register [token]" + " || connect " " || delete "), N_(" list: list workspaces\n" "register: add a slack workspace\n" + " connect: connect to a slack workspace\n" " delete: delete a slack workspace\n"), "list" " || register %(slack_token)" + " || connect %(slack_workspace)" " || delete %(slack_workspace)", &slack_command_slack, NULL, NULL); } diff --git a/slack-config.c b/slack-config.c index a6c59cb..f6a8f2c 100644 --- a/slack-config.c +++ b/slack-config.c @@ -17,17 +17,27 @@ int slack_config_workspace_check_value_cb(const void *pointer, void *data, struct t_config_option *option, const char *value) { + (void) pointer; + (void) data; + (void) option; + (void) value; return 1; } void slack_config_workspace_change_cb(const void *pointer, void *data, struct t_config_option *option) { + (void) pointer; + (void) data; + (void) option; } void slack_config_workspace_default_change_cb(const void *pointer, void *data, struct t_config_option *option) { + (void) pointer; + (void) data; + (void) option; } struct t_config_option * @@ -125,7 +135,7 @@ int slack_config_workspace_read_cb (const void *pointer, void *data, { struct t_slack_workspace *ptr_workspace; int index_option, rc, i; - char *pos_option, *workspace_name; + char *pos_option, *workspace_domain; /* make C compiler happy */ (void) pointer; @@ -140,17 +150,17 @@ int slack_config_workspace_read_cb (const void *pointer, void *data, pos_option = strrchr(option_name, '.'); if (pos_option) { - workspace_name = weechat_strndup(option_name, - pos_option - option_name); + workspace_domain = weechat_strndup(option_name, + pos_option - option_name); pos_option++; - if (workspace_name) + if (workspace_domain) { index_option = slack_workspace_search_option(pos_option); if (index_option >= 0) { - ptr_workspace = slack_workspace_search(workspace_name); + ptr_workspace = slack_workspace_search(workspace_domain); if (!ptr_workspace) - ptr_workspace = slack_workspace_alloc(workspace_name); + ptr_workspace = slack_workspace_alloc(workspace_domain); if (ptr_workspace) { if (ptr_workspace->reloading_from_config @@ -172,10 +182,10 @@ int slack_config_workspace_read_cb (const void *pointer, void *data, NULL, _("%s%s: error adding workspace \"%s\""), weechat_prefix("error"), SLACK_PLUGIN_NAME, - workspace_name); + workspace_domain); } } - free(workspace_name); + free(workspace_domain); } } } @@ -272,12 +282,16 @@ int slack_config_init() int slack_config_read() { - return 1; + int rc; + + rc = weechat_config_read(slack_config_file); + + return rc; } int slack_config_write() { - return 1; + return weechat_config_write(slack_config_file); } void slack_config_free() diff --git a/slack-input.c b/slack-input.c new file mode 100644 index 0000000..ec36d5d --- /dev/null +++ b/slack-input.c @@ -0,0 +1,20 @@ +#include "weechat-plugin.h" +#include "slack-input.h" + +int slack_input_data(struct t_gui_buffer *buffer, const char *input_data) +{ + (void) buffer; + (void) input_data; + + return WEECHAT_RC_OK; +} + +int slack_input_data_cb(const void *pointer, void *data, + struct t_gui_buffer *buffer, + const char *input_data) +{ + (void) pointer; + (void) data; + + return slack_input_data(buffer, input_data); +} diff --git a/slack-input.h b/slack-input.h new file mode 100644 index 0000000..e3ac745 --- /dev/null +++ b/slack-input.h @@ -0,0 +1,8 @@ +#ifndef _SLACK_INPUT_H_ +#define _SLACK_INPUT_H_ + +int slack_input_data_cb(const void *pointer, void *data, + struct t_gui_buffer *buffer, + const char *input_data); + +#endif /*SLACK_INPUT_H*/ diff --git a/slack-oauth.c b/slack-oauth.c index c18836f..ca46285 100644 --- a/slack-oauth.c +++ b/slack-oauth.c @@ -19,13 +19,13 @@ static struct lws_context *context = NULL; static struct t_hook *slack_oauth_hook_timer = NULL; -static int json_valid(json_object *object) +static inline int json_valid(json_object *object) { if (!object) { weechat_printf( NULL, - _("%s%s: Error retrieving token: unexpected response from server"), + _("%s%s: error retrieving token: unexpected response from server"), weechat_prefix("error"), SLACK_PLUGIN_NAME); return 0; } @@ -54,7 +54,7 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, status = lws_http_client_http_response(wsi); weechat_printf( NULL, - _("%s%s: Retrieving token... (%d)"), + _("%s%s: retrieving token... (%d)"), weechat_prefix("network"), SLACK_PLUGIN_NAME, status); break; @@ -67,7 +67,7 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, weechat_printf( NULL, - _("%s%s: Got token: %s"), + _("%s%s: got response: %s"), weechat_prefix("network"), SLACK_PLUGIN_NAME, json_string); @@ -92,7 +92,7 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, weechat_printf( NULL, - _("%s%s: Retrieved token: %s"), + _("%s%s: retrieved token: %s"), weechat_prefix("network"), SLACK_PLUGIN_NAME, json_object_get_string(token)); @@ -110,7 +110,7 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, weechat_printf( NULL, - _("%s%s: Failed to retrieve token: %s"), + _("%s%s: failed to retrieve token: %s"), weechat_prefix("error"), SLACK_PLUGIN_NAME, json_object_get_string(error)); } @@ -133,10 +133,6 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, return 0; /* don't passthru */ case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: - client_wsi = NULL; - lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ - break; - case LWS_CALLBACK_CLOSED_CLIENT_HTTP: client_wsi = NULL; lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ @@ -161,6 +157,10 @@ static const struct lws_protocols protocols[] = { int slack_oauth_timer_cb(const void *pointer, void *data, int remaining_calls) { + (void) pointer; + (void) data; + (void) remaining_calls; + if (n >= 0 && client_wsi) { n = lws_service(context, 0); @@ -183,12 +183,19 @@ void slack_oauth_request_token(char *code, void (*callback)(char *token)) struct lws_context_creation_info info; struct lws_client_connect_info i; + if (client_wsi) + { + weechat_printf( + NULL, + _("%s%s: error: a registration is already in progress"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return; + } + size_t urilen = snprintf(NULL, 0, endpoint, SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, code) + 1; uri = malloc(urilen); snprintf(uri, urilen, endpoint, SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, code); - lws_set_log_level(0, NULL); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ @@ -207,7 +214,7 @@ void slack_oauth_request_token(char *code, void (*callback)(char *token)) { weechat_printf( NULL, - _("%s%s: Contacting slack.com:443"), + _("%s%s: contacting slack.com:443"), weechat_prefix("network"), SLACK_PLUGIN_NAME); } diff --git a/slack-teaminfo.c b/slack-teaminfo.c new file mode 100644 index 0000000..e8185ad --- /dev/null +++ b/slack-teaminfo.c @@ -0,0 +1,342 @@ +#include +#include +#include +#include + +#include "weechat-plugin.h" +#include "slack.h" +#include "slack-teaminfo.h" + +static void (*weechat_callback)(struct t_slack_teaminfo *slack_teaminfo); + +static const char *const endpoint = "/api/team.info?" + "token=%s"; +static char *uri; + +static int n = 0; +static struct lws *client_wsi = NULL; +static struct lws_context *context = NULL; + +static struct t_hook *slack_teaminfo_hook_timer = NULL; + +struct t_json_chunk +{ + char *data; + struct t_json_chunk *next; +}; + +static struct t_json_chunk *slack_teaminfo_chunks = NULL; +static struct t_slack_teaminfo slack_teaminfo; + +static inline int json_valid(json_object *object) +{ + if (!object) + { + weechat_printf( + NULL, + _("%s%s: Error retrieving workspace info: unexpected response from server"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return 0; + } + + return 1; +} + +static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + int status; + + switch (reason) + { + /* because we are protocols[0] ... */ + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + weechat_printf( + NULL, + _("%s%s: error connecting to slack: %s"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, + in ? (char *)in : "(null)"); + client_wsi = NULL; + break; + + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: + status = lws_http_client_http_response(wsi); + weechat_printf( + NULL, + _("%s%s: retrieving workspace details... (%d)"), + weechat_prefix("network"), SLACK_PLUGIN_NAME, + 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 (slack_teaminfo_chunks) + { + for (last_chunk = slack_teaminfo_chunks; last_chunk->next; + last_chunk = last_chunk->next); + last_chunk->next = new_chunk; + } + else + { + slack_teaminfo_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, *team; + json_object *id, *name, *domain, *email_domain; + struct t_json_chunk *chunk_ptr; + + chunk_count = 0; + if (slack_teaminfo_chunks) + { + chunk_count++; + for (chunk_ptr = slack_teaminfo_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 = slack_teaminfo_chunks; + for (i = 0; i < chunk_count; i++) + { + strncat(json_string, chunk_ptr->data, 1024); + chunk_ptr = chunk_ptr->next; + + free(slack_teaminfo_chunks->data); + free(slack_teaminfo_chunks); + slack_teaminfo_chunks = chunk_ptr; + } + + weechat_printf( + NULL, + _("%s%s: got response: %s"), + weechat_prefix("network"), SLACK_PLUGIN_NAME, + json_string); + + response = json_tokener_parse(json_string); + ok = json_object_object_get(response, "ok"); + if (!json_valid(ok)) + { + json_object_put(response); + free(json_string); + return 0; + } + + if(json_object_get_boolean(ok)) + { + team = json_object_object_get(response, "team"); + if (!json_valid(team)) + { + json_object_put(response); + free(json_string); + return 0; + } + + id = json_object_object_get(team, "id"); + if (!json_valid(id)) + { + json_object_put(response); + free(json_string); + return 0; + } + + name = json_object_object_get(team, "name"); + if (!json_valid(name)) + { + json_object_put(response); + free(json_string); + return 0; + } + + domain = json_object_object_get(team, "domain"); + if (!json_valid(domain)) + { + json_object_put(response); + free(json_string); + return 0; + } + + email_domain = json_object_object_get(team, "email_domain"); + if (!json_valid(email_domain)) + { + json_object_put(response); + free(json_string); + return 0; + } + + weechat_printf( + NULL, + _("%s%s: retrieved workspace details for %s@%s"), + weechat_prefix("network"), SLACK_PLUGIN_NAME, + json_object_get_string(name), json_object_get_string(domain)); + + slack_teaminfo.id = json_object_get_string(id); + slack_teaminfo.name = json_object_get_string(name); + slack_teaminfo.domain = json_object_get_string(domain); + slack_teaminfo.email_domain = json_object_get_string(email_domain); + + weechat_callback(&slack_teaminfo); + } + else + { + error = json_object_object_get(response, "error"); + if (!json_valid(error)) + { + json_object_put(response); + free(json_string); + return 0; + } + + weechat_printf( + NULL, + _("%s%s: failed to retrieve workspace details: %s"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, + json_object_get_string(error)); + } + + json_object_put(response); + free(json_string); + } + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + client_wsi = NULL; + 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 } +}; + +int slack_teaminfo_timer_cb(const void *pointer, void *data, int remaining_calls) +{ + (void) pointer; + (void) data; + (void) remaining_calls; + + if (n >= 0 && client_wsi) + { + n = lws_service(context, 0); + } + else if (context) + { + lws_context_destroy(context); + context = NULL; + free(uri); + + if (slack_teaminfo_hook_timer) + weechat_unhook(slack_teaminfo_hook_timer); + } + + return WEECHAT_RC_OK; +} + +void slack_teaminfo_fetch(char *token, void (*callback)(struct t_slack_teaminfo *slack_teaminfo)) +{ + struct lws_context_creation_info info; + struct lws_client_connect_info i; + + if (client_wsi) + { + weechat_printf( + NULL, + _("%s%s: error: a registration is already in progress"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return; + } + + slack_teaminfo.token = strdup(token); + + size_t urilen = snprintf(NULL, 0, endpoint, token) + 1; + uri = malloc(urilen); + snprintf(uri, urilen, endpoint, token); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ + info.protocols = protocols; + + context = lws_create_context(&info); + if (!context) + { + weechat_printf( + NULL, + _("%s%s: error connecting to slack: lws init failed"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return; + } + else + { + weechat_printf( + NULL, + _("%s%s: contacting slack.com:443"), + weechat_prefix("network"), SLACK_PLUGIN_NAME); + } + + memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ + i.context = context; + i.ssl_connection = LCCSCF_USE_SSL; + i.port = 443; + i.address = "slack.com"; + i.path = uri; + i.host = i.address; + i.origin = i.address; + i.method = "GET"; + i.protocol = protocols[0].name; + i.pwsi = &client_wsi; + + lws_client_connect_via_info(&i); + + slack_teaminfo_hook_timer = weechat_hook_timer(1 * 1000, 0, 0, + &slack_teaminfo_timer_cb, + NULL, NULL); + + weechat_callback = callback; +} + +void free_teaminfo(struct t_slack_teaminfo *teaminfo) +{ + free(teaminfo->token); +} diff --git a/slack-teaminfo.h b/slack-teaminfo.h new file mode 100644 index 0000000..cf37fe9 --- /dev/null +++ b/slack-teaminfo.h @@ -0,0 +1,16 @@ +#ifndef _SLACK_TEAMINFO_H_ +#define _SLACK_TEAMINFO_H_ + +struct t_slack_teaminfo +{ + const char *id; + const char *name; + const char *domain; + const char *email_domain; + char *token; +}; + +extern void slack_teaminfo_fetch(char *token, void (*callback)(struct t_slack_teaminfo *slack_teaminfo)); +extern void free_teaminfo(struct t_slack_teaminfo *teaminfo); + +#endif /*SLACK_TEAMINFO_H*/ diff --git a/slack-workspace.c b/slack-workspace.c index e2e3c62..da55346 100644 --- a/slack-workspace.c +++ b/slack-workspace.c @@ -1,11 +1,17 @@ +#include +#include #include #include #include +#include #include "weechat-plugin.h" #include "slack.h" +#include "slack-buffer.h" #include "slack-config.h" +#include "slack-input.h" #include "slack-workspace.h" +#include "slack-api.h" struct t_slack_workspace *slack_workspaces = NULL; struct t_slack_workspace *last_slack_workspace = NULL; @@ -14,17 +20,264 @@ char *slack_workspace_options[SLACK_WORKSPACE_NUM_OPTIONS][2] = { { "token", "" }, }; -struct t_slack_workspace *slack_workspace_search(const char *workspace_name) +static const char *const endpoint = "/api/rtm.connect?" + "token=%s&batch_presence_aware=true&presence_sub=false"; + +static inline int json_valid(json_object *object, struct t_slack_workspace *workspace) +{ + if (!object) + { + weechat_printf( + workspace->buffer, + _("%s%s: Error requesting websocket: unexpected response from server"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + 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_workspace *workspace = (struct t_slack_workspace *)user; + int status; + + switch (reason) + { + /* because we are protocols[0] ... */ + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + weechat_printf( + workspace->buffer, + _("%s%s: error connecting to slack: %s"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, + in ? (char *)in : "(null)"); + workspace->client_wsi = NULL; + break; + + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: + status = lws_http_client_http_response(wsi); + weechat_printf( + workspace->buffer, + _("%s%s: requesting a websocket... (%d)"), + weechat_prefix("network"), SLACK_PLUGIN_NAME, + 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 (workspace->json_chunks) + { + for (last_chunk = workspace->json_chunks; last_chunk->next; + last_chunk = last_chunk->next); + last_chunk->next = new_chunk; + } + else + { + workspace->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, *self, *team, *url; + json_object *id, *name, *domain; + struct t_json_chunk *chunk_ptr; + + chunk_count = 0; + if (workspace->json_chunks) + { + chunk_count++; + for (chunk_ptr = workspace->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 = workspace->json_chunks; + for (i = 0; i < chunk_count; i++) + { + strncat(json_string, chunk_ptr->data, 1024); + chunk_ptr = chunk_ptr->next; + + free(workspace->json_chunks->data); + free(workspace->json_chunks); + workspace->json_chunks = chunk_ptr; + } + + weechat_printf( + workspace->buffer, + _("%s%s: got response: %s"), + weechat_prefix("network"), SLACK_PLUGIN_NAME, + json_string); + + response = json_tokener_parse(json_string); + ok = json_object_object_get(response, "ok"); + if (!json_valid(ok, workspace)) + { + json_object_put(response); + free(json_string); + return 0; + } + + if(json_object_get_boolean(ok)) + { + self = json_object_object_get(response, "self"); + if (!json_valid(self, workspace)) + { + json_object_put(response); + free(json_string); + return 0; + } + else + { + id = json_object_object_get(self, "id"); + if (!json_valid(id, workspace)) + { + json_object_put(response); + free(json_string); + return 0; + } + workspace->user = strdup(json_object_get_string(id)); + + name = json_object_object_get(self, "name"); + if (!json_valid(name, workspace)) + { + json_object_put(response); + free(json_string); + return 0; + } + workspace->nick = strdup(json_object_get_string(name)); + } + + team = json_object_object_get(response, "team"); + if (!json_valid(team, workspace)) + { + json_object_put(response); + free(json_string); + return 0; + } + else + { + domain = json_object_object_get(team, "domain"); + if (!json_valid(domain, workspace)) + { + json_object_put(response); + free(json_string); + return 0; + } + + id = json_object_object_get(team, "id"); + if (!json_valid(id, workspace)) + { + json_object_put(response); + free(json_string); + return 0; + } + workspace->id = strdup(json_object_get_string(id)); + + name = json_object_object_get(team, "name"); + if (!json_valid(name, workspace)) + { + json_object_put(response); + free(json_string); + return 0; + } + workspace->name = strdup(json_object_get_string(name)); + } + + url = json_object_object_get(response, "url"); + if (!json_valid(url, workspace)) + { + json_object_put(response); + free(json_string); + return 0; + } + + workspace->ws_url = strdup(json_object_get_string(url)); + } + else + { + error = json_object_object_get(response, "error"); + if (!json_valid(error, workspace)) + { + json_object_put(response); + free(json_string); + return 0; + } + + weechat_printf( + workspace->buffer, + _("%s%s: failed to request websocket: %s"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, + json_object_get_string(error)); + } + + json_object_put(response); + free(json_string); + } + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + workspace->client_wsi = NULL; + 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_workspace *slack_workspace_search(const char *workspace_domain) { struct t_slack_workspace *ptr_workspace; - if (!workspace_name) + if (!workspace_domain) return NULL; for (ptr_workspace = slack_workspaces; ptr_workspace; ptr_workspace = ptr_workspace->next_workspace) { - if (strcmp(ptr_workspace->name, workspace_name) == 0) + if (strcmp(ptr_workspace->domain, workspace_domain) == 0) return ptr_workspace; } @@ -32,17 +285,17 @@ struct t_slack_workspace *slack_workspace_search(const char *workspace_name) return NULL; } -struct t_slack_workspace *slack_workspace_casesearch (const char *workspace_name) +struct t_slack_workspace *slack_workspace_casesearch (const char *workspace_domain) { struct t_slack_workspace *ptr_workspace; - if (!workspace_name) + if (!workspace_domain) return NULL; for (ptr_workspace = slack_workspaces; ptr_workspace; ptr_workspace = ptr_workspace->next_workspace) { - if (weechat_strcasecmp (ptr_workspace->name, workspace_name) == 0) + if (weechat_strcasecmp (ptr_workspace->domain, workspace_domain) == 0) return ptr_workspace; } @@ -67,13 +320,13 @@ int slack_workspace_search_option(const char *option_name) return -1; } -struct t_slack_workspace *slack_workspace_alloc(const char *name) +struct t_slack_workspace *slack_workspace_alloc(const char *domain) { struct t_slack_workspace *new_workspace; int i, length; char *option_name; - if (slack_workspace_casesearch(name)) + if (slack_workspace_casesearch(domain)) return NULL; /* alloc memory for new workspace */ @@ -81,8 +334,8 @@ struct t_slack_workspace *slack_workspace_alloc(const char *name) if (!new_workspace) { weechat_printf(NULL, - _("%s%s: error when allocating new workspace"), - weechat_prefix("error"), SLACK_PLUGIN_NAME); + _("%s%s: error when allocating new workspace"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); return NULL; } @@ -95,19 +348,36 @@ struct t_slack_workspace *slack_workspace_alloc(const char *name) slack_workspaces = new_workspace; last_slack_workspace = new_workspace; + /* set properties */ + new_workspace->id = NULL; + new_workspace->name = NULL; + /* set name */ - new_workspace->name = strdup(name); + new_workspace->domain = strdup(domain); /* internal vars */ new_workspace->reloading_from_config = 0; new_workspace->reloaded_from_config = 0; new_workspace->is_connected = 0; + new_workspace->disconnected = 0; + + new_workspace->uri = NULL; + new_workspace->ws_url = NULL; + new_workspace->client_wsi = NULL; + new_workspace->context = NULL; + new_workspace->json_chunks = NULL; + + new_workspace->user = NULL; + new_workspace->nick = NULL; + + new_workspace->buffer = NULL; + new_workspace->buffer_as_string = NULL; /* create options with null value */ for (i = 0; i < SLACK_WORKSPACE_NUM_OPTIONS; i++) { - length = strlen(new_workspace->name) + 1 + + length = strlen(new_workspace->domain) + 1 + strlen(slack_workspace_options[i][0]) + 512 + /* inherited option name(slack.workspace_default.xxx) */ 1; @@ -115,7 +385,7 @@ struct t_slack_workspace *slack_workspace_alloc(const char *name) if (option_name) { snprintf(option_name, length, "%s.%s << slack.workspace_default.%s", - new_workspace->name, + new_workspace->domain, slack_workspace_options[i][0], slack_workspace_options[i][0]); new_workspace->options[i] = slack_config_workspace_new_option( @@ -140,3 +410,378 @@ struct t_slack_workspace *slack_workspace_alloc(const char *name) return new_workspace; } + +void slack_workspace_free_data(struct t_slack_workspace *workspace) +{ + int i; + + if (!workspace) + return; + + /* free linked lists */ + /* + for (i = 0; i < IRC_SERVER_NUM_OUTQUEUES_PRIO; i++) + { + slack_workspace_outqueue_free_all(workspace, i); + } + slack_redirect_free_all(workspace); + slack_notify_free_all(workspace); + slack_channel_free_all(workspace); + */ + + /* free hashtables */ + /* + weechat_hashtable_free(workspace->join_manual); + weechat_hashtable_free(workspace->join_channel_key); + weechat_hashtable_free(workspace->join_noswitch); + */ + + /* free workspace data */ + for (i = 0; i < SLACK_WORKSPACE_NUM_OPTIONS; i++) + { + if (workspace->options[i]) + weechat_config_option_free(workspace->options[i]); + } + if (workspace->id) + free(workspace->id); + if (workspace->name) + free(workspace->name); + if (workspace->domain) + free(workspace->domain); + + if (workspace->uri) + free(workspace->uri); + if (workspace->ws_url) + free(workspace->ws_url); + if (workspace->client_wsi) + free(workspace->client_wsi); + if (workspace->context) + free(workspace->context); + while (workspace->json_chunks) + { + struct t_json_chunk *chunk_ptr = workspace->json_chunks->next; + + free(workspace->json_chunks->data); + free(workspace->json_chunks); + workspace->json_chunks = chunk_ptr; + } + + if (workspace->user) + free(workspace->user); + if (workspace->nick) + free(workspace->nick); + + if (workspace->buffer_as_string) + free(workspace->buffer_as_string); +} + +void slack_workspace_free(struct t_slack_workspace *workspace) +{ + struct t_slack_workspace *new_slack_workspaces; + + if (!workspace) + return; + + /* + * close workspace buffer (and all channels/privates) + * (only if we are not in a /upgrade, because during upgrade we want to + * keep connections and closing workspace buffer would disconnect from workspace) + */ + if (workspace->buffer) + weechat_buffer_close(workspace->buffer); + + /* remove workspace from queue */ + if (last_slack_workspace == workspace) + last_slack_workspace = workspace->prev_workspace; + if (workspace->prev_workspace) + { + (workspace->prev_workspace)->next_workspace = workspace->next_workspace; + new_slack_workspaces = slack_workspaces; + } + else + new_slack_workspaces = workspace->next_workspace; + + if (workspace->next_workspace) + (workspace->next_workspace)->prev_workspace = workspace->prev_workspace; + + slack_workspace_free_data(workspace); + free(workspace); + slack_workspaces = new_slack_workspaces; +} + +void slack_workspace_free_all() +{ + /* for each workspace in memory, remove it */ + while (slack_workspaces) + { + slack_workspace_free(slack_workspaces); + } +} + +void slack_workspace_disconnect(struct t_slack_workspace *workspace, + int switch_address, int reconnect) +{ + (void) switch_address; + (void) reconnect; + + struct t_slack_channel *ptr_channel; + (void) ptr_channel; + + if (workspace->is_connected) + { + /* + * remove all nicks and write disconnection message on each + * channel/private buffer + */ + /* + for (ptr_channel = workspace->channels; ptr_channel; + ptr_channel = ptr_channel->next_channel) + { + slack_nick_free_all(workspace, ptr_channel); + if (ptr_channel->hook_autorejoin) + { + weechat_unhook(ptr_channel->hook_autorejoin); + ptr_channel->hook_autorejoin = NULL; + } + weechat_buffer_set(ptr_channel->buffer, "localvar_del_away", ""); + weechat_printf( + ptr_channel->buffer, + _("%s%s: disconnected from workspace"), + weechat_prefix("network"), SLACK_PLUGIN_NAME); + } + */ + /* remove away status on workspace buffer */ + //weechat_buffer_set(workspace->buffer, "localvar_del_away", ""); + } + + /* + slack_workspace_close_connection(workspace); + + if (workspace->buffer) + { + weechat_printf( + workspace->buffer, + _("%s%s: disconnected from workspace"), + weechat_prefix ("network"), SLACK_PLUGIN_NAME); + } + + workspace->current_retry = 0; + + if (switch_address) + slack_workspace_switch_address(workspace, 0); + else + slack_workspace_set_index_current_address(workspace, 0); + + if (workspace->nick_modes) + { + free (workspace->nick_modes); + workspace->nick_modes = NULL; + weechat_bar_item_update ("input_prompt"); + weechat_bar_item_update ("slack_nick_modes"); + } + workspace->cap_away_notify = 0; + workspace->cap_account_notify = 0; + workspace->cap_extended_join = 0; + workspace->is_away = 0; + workspace->away_time = 0; + workspace->lag = 0; + workspace->lag_displayed = -1; + workspace->lag_check_time.tv_sec = 0; + workspace->lag_check_time.tv_usec = 0; + workspace->lag_next_check = time (NULL) + + weechat_config_integer (slack_config_network_lag_check); + workspace->lag_last_refresh = 0; + slack_workspace_set_lag (workspace); + workspace->monitor = 0; + workspace->monitor_time = 0; + + if (reconnect + && IRC_SERVER_OPTION_BOOLEAN(workspace, IRC_SERVER_OPTION_AUTORECONNECT)) + slack_workspace_reconnect_schedule(workspace); + else + { + workspace->reconnect_delay = 0; + workspace->reconnect_start = 0; + } + */ + + /* discard current nick if no reconnection asked */ + /* + if (!reconnect && workspace->nick) + slack_workspace_set_nick(workspace, NULL); + + slack_workspace_set_buffer_title(workspace); + + workspace->disconnected = 1; + */ + + /* send signal "slack_workspace_disconnected" with workspace name */ + /* + (void) weechat_hook_signal_send("slack_workspace_disconnected", + WEECHAT_HOOK_SIGNAL_STRING, workspace->name); + */ +} + +void slack_workspace_disconnect_all() +{ + struct t_slack_workspace *ptr_workspace; + + for (ptr_workspace = slack_workspaces; ptr_workspace; + ptr_workspace = ptr_workspace->next_workspace) + { + slack_workspace_disconnect(ptr_workspace, 0, 0); + } +} + +struct t_gui_buffer *slack_workspace_create_buffer(struct t_slack_workspace *workspace) +{ + char buffer_name[256], charset_modifier[256]; + + snprintf(buffer_name, sizeof(buffer_name), + "slack.%s", workspace->domain); + workspace->buffer = weechat_buffer_new(buffer_name, + &slack_input_data_cb, NULL, NULL, + &slack_buffer_close_cb, NULL, NULL); + if (!workspace->buffer) + return NULL; + + if (!weechat_buffer_get_integer(workspace->buffer, "short_name_is_set")) + weechat_buffer_set(workspace->buffer, "short_name", workspace->domain); + weechat_buffer_set(workspace->buffer, "localvar_set_type", "server"); + weechat_buffer_set(workspace->buffer, "localvar_set_server", workspace->domain); + weechat_buffer_set(workspace->buffer, "localvar_set_channel", workspace->domain); + snprintf(charset_modifier, sizeof (charset_modifier), + "slack.%s", workspace->domain); + weechat_buffer_set(workspace->buffer, "localvar_set_charset_modifier", + charset_modifier); + weechat_buffer_set(workspace->buffer, "title", + (workspace->name) ? workspace->name : ""); + + return workspace->buffer; +} + +void slack_workspace_close_connection(struct t_slack_workspace *workspace) +{ + workspace->is_connected = 0; + workspace->client_wsi = NULL; + workspace->context = NULL; +} + +void slack_workspace_websocket_create(struct t_slack_workspace *workspace) +{ + struct lws_context_creation_info info; + struct lws_client_connect_info i; + const char *token; + + if (workspace->client_wsi) + { + weechat_printf( + workspace->buffer, + _("%s%s: error: a websocket already exists"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return; + } + + token = weechat_config_string(workspace->options[SLACK_WORKSPACE_OPTION_TOKEN]); + + size_t urilen = snprintf(NULL, 0, endpoint, token) + 1; + workspace->uri = malloc(urilen); + snprintf(workspace->uri, urilen, endpoint, token); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ + info.protocols = protocols; + + workspace->context = lws_create_context(&info); + if (!workspace->context) + { + weechat_printf( + workspace->buffer, + _("%s%s: error connecting to slack: lws init failed"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return; + } + else + { + weechat_printf( + workspace->buffer, + _("%s%s: contacting slack.com:443"), + weechat_prefix("network"), SLACK_PLUGIN_NAME); + } + + memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ + i.context = workspace->context; + i.ssl_connection = LCCSCF_USE_SSL; + i.port = 443; + i.address = "slack.com"; + i.path = workspace->uri; + i.host = i.address; + i.origin = i.address; + i.method = "GET"; + i.protocol = protocols[0].name; + i.pwsi = &workspace->client_wsi; + i.userdata = workspace; + + lws_client_connect_via_info(&i); + + workspace->is_connected = 1; +} + +int slack_workspace_connect(struct t_slack_workspace *workspace) +{ + workspace->disconnected = 0; + + if (!workspace->buffer) + { + if (!slack_workspace_create_buffer(workspace)) + return 0; + weechat_buffer_set(workspace->buffer, "display", "auto"); + } + + slack_workspace_close_connection(workspace); + + slack_workspace_websocket_create(workspace); + + return 1; +} + +int slack_workspace_timer_cb(const void *pointer, void *data, int remaining_calls) +{ + struct t_slack_workspace *ptr_workspace; + + /* make C compiler happy */ + (void) pointer; + (void) data; + (void) remaining_calls; + + for (ptr_workspace = slack_workspaces; ptr_workspace; + ptr_workspace = ptr_workspace->next_workspace) + { + if (!ptr_workspace->is_connected) + continue; + + if (ptr_workspace->client_wsi) + { + lws_service(ptr_workspace->context, 0); + } + else if (ptr_workspace->context) + { + lws_context_destroy(ptr_workspace->context); + ptr_workspace->context = NULL; + if (ptr_workspace->uri) + { + free(ptr_workspace->uri); + ptr_workspace->uri = NULL; + } + if (ptr_workspace->ws_url) + { + slack_api_connect(ptr_workspace); + free(ptr_workspace->ws_url); + ptr_workspace->ws_url = NULL; + } + } + } + + return WEECHAT_RC_OK; +} diff --git a/slack-workspace.h b/slack-workspace.h index 2b79eb6..216eeb7 100644 --- a/slack-workspace.h +++ b/slack-workspace.h @@ -10,24 +10,54 @@ enum t_slack_workspace_option SLACK_WORKSPACE_NUM_OPTIONS, }; +struct t_json_chunk +{ + char *data; + struct t_json_chunk *next; +}; + struct t_slack_workspace { + char *id; char *name; + char *domain; struct t_config_option *options[SLACK_WORKSPACE_NUM_OPTIONS]; int reloading_from_config; int reloaded_from_config; int is_connected; + int disconnected; + + char *uri; + char *ws_url; + struct lws *client_wsi; + struct lws_context *context; + struct t_json_chunk *json_chunks; + + char *user; + char *nick; + struct t_gui_buffer *buffer; + char *buffer_as_string; struct t_slack_workspace *prev_workspace; struct t_slack_workspace *next_workspace; }; extern char *slack_workspace_options[][2]; -struct t_slack_workspace *slack_workspace_search(const char *workspace_name); +struct t_slack_workspace *slack_workspace_search(const char *workspace_domain); +struct t_slack_workspace *slack_workspace_casesearch (const char *workspace_domain); int slack_workspace_search_option(const char *option_name); -struct t_slack_workspace *slack_workspace_alloc(const char *name); +struct t_slack_workspace *slack_workspace_alloc(const char *domain); +void slack_workspace_free_data(struct t_slack_workspace *workspace); +void slack_workspace_free(struct t_slack_workspace *workspace); +void slack_workspace_free_all(); +void slack_workspace_disconnect(struct t_slack_workspace *workspace, + int switch_address, int reconnect); +void slack_workspace_disconnect_all(); +void slack_workspace_close_connection(struct t_slack_workspace *workspace); +int slack_workspace_connect(struct t_slack_workspace *workspace); +int slack_workspace_timer_cb(const void *pointer, void *data, int remaining_calls); #endif /*SLACK_WORKSPACE_H*/ diff --git a/slack.c b/slack.c index f1ba914..528e4b1 100644 --- a/slack.c +++ b/slack.c @@ -1,11 +1,13 @@ #include #include #include +#include #include "weechat-plugin.h" #include "slack.h" #include "slack-config.h" #include "slack-command.h" +#include "slack-workspace.h" WEECHAT_PLUGIN_NAME(SLACK_PLUGIN_NAME); @@ -17,6 +19,20 @@ WEECHAT_PLUGIN_PRIORITY(6000); struct t_weechat_plugin *weechat_slack_plugin = NULL; +struct t_hook *slack_hook_timer = NULL; + +void slack_lwsl_emit_weechat(int level, const char *line) +{ + char buf[50]; + lwsl_timestamp(level, buf, sizeof(buf)); + + weechat_printf( + NULL, + _("%s%s: %s%s"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, + buf, line); +} + int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char *argv[]) { (void) argc; @@ -24,6 +40,11 @@ int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char *argv[]) weechat_plugin = plugin; + lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG + | LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT + | LLL_LATENCY | LLL_USER | LLL_COUNT, + slack_lwsl_emit_weechat); + if (!slack_config_init()) return WEECHAT_RC_ERROR; @@ -31,6 +52,10 @@ int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char *argv[]) slack_command_init(); + slack_hook_timer = weechat_hook_timer(1 * 1000, 0, 0, + &slack_workspace_timer_cb, + NULL, NULL); + return WEECHAT_RC_OK; } @@ -39,5 +64,14 @@ int weechat_plugin_end(struct t_weechat_plugin *plugin) /* make C compiler happy */ (void) plugin; + if (slack_hook_timer) + weechat_unhook(slack_hook_timer); + + slack_config_write(); + + slack_workspace_disconnect_all(); + + slack_workspace_free_all(); + return WEECHAT_RC_OK; }