diff --git a/Makefile b/Makefile index 385ddda..63bb5ce 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ SRCS=slack.c \ api/message/slack-api-message-slackbot-response.c \ api/message/slack-api-message-me-message.c \ api/message/slack-api-message-unimplemented.c \ + request/slack-request-chat-memessage.c \ request/slack-request-chat-postmessage.c \ request/slack-request-channels-list.c \ request/slack-request-conversations-members.c \ diff --git a/README.org b/README.org index 62feb09..1a0d9c0 100644 --- a/README.org +++ b/README.org @@ -44,7 +44,7 @@ ** TODO [#A] Implement essential api endpoints and events - [X] +Implement handling api message =message.me_message= (see [[http://github.com/bqv/weechat-slack/issues/5][#5]])+ - - [ ] Implement sending request =chat.meMessage= (see [[http://github.com/bqv/weechat-slack/issues/5][#5]]) + - [X] +Implement sending request =chat.meMessage= (see [[http://github.com/bqv/weechat-slack/issues/5][#5]])+ - [ ] Implement handling api message =message.thread_broadcast= - [X] +Implement handling api message =message.bot_message= (see [[http://github.com/bqv/weechat-slack/issues/2][#2]])+ - [ ] Implement handling api message =message.message_changed= @@ -62,6 +62,7 @@ - [ ] Complete api event set ** TODO [#C] Implement full weechat functionality - [ ] Hook buffer closes + - [ ] Relay compatibility * Contributing diff --git a/request/slack-request-chat-memessage.c b/request/slack-request-chat-memessage.c new file mode 100644 index 0000000..38a0487 --- /dev/null +++ b/request/slack-request-chat-memessage.c @@ -0,0 +1,270 @@ +// 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-user.h" +#include "../request/slack-request-chat-memessage.h" + +static const char *const endpoint = "/api/chat.meMessage?" + "token=%s&channel=%s&text=%s&"; + +static inline int json_valid(json_object *object, struct t_slack_workspace *workspace) +{ + if (!object) + { + weechat_printf( + workspace->buffer, + _("%s%s: error sending me-message: 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) sending me-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 send me-message: %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_chat_memessage( + 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-memessage.h b/request/slack-request-chat-memessage.h new file mode 100644 index 0000000..d93c95b --- /dev/null +++ b/request/slack-request-chat-memessage.h @@ -0,0 +1,13 @@ +// 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_CHAT_MEMESSAGE_H_ +#define _SLACK_REQUEST_CHAT_MEMESSAGE_H_ + +struct t_slack_request *slack_request_chat_memessage( + struct t_slack_workspace *workspace, + const char *token, const char *channel, + const char *text); + +#endif /*SLACK_REQUEST_CHAT_MEMESSAGE_H*/ diff --git a/slack-command.c b/slack-command.c index 312ddca..b2dd04d 100644 --- a/slack-command.c +++ b/slack-command.c @@ -2,15 +2,20 @@ // 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 "weechat-plugin.h" #include "slack.h" -#include "slack-command.h" #include "slack-oauth.h" #include "slack-teaminfo.h" #include "slack-workspace.h" +#include "slack-channel.h" +#include "slack-buffer.h" +#include "slack-message.h" +#include "slack-command.h" +#include "request/slack-request-chat-memessage.h" void slack_command_display_workspace(struct t_slack_workspace *workspace) { @@ -30,7 +35,7 @@ void slack_command_display_workspace(struct t_slack_workspace *workspace) weechat_color("chat_delimiters"), weechat_color("chat_server"), (workspace->name) ? - workspace->name : "???", + workspace->name : "???", weechat_color("chat_delimiters"), weechat_color("reset"), (workspace->is_connected) ? @@ -52,7 +57,7 @@ void slack_command_display_workspace(struct t_slack_workspace *workspace) weechat_color("chat_delimiters"), weechat_color("chat_server"), (workspace->name) ? - workspace->name : "???", + workspace->name : "???", weechat_color("chat_delimiters"), weechat_color("reset")); } @@ -140,18 +145,18 @@ void slack_command_add_workspace(struct t_slack_teaminfo *slack_teaminfo) 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")); + 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); } @@ -214,8 +219,8 @@ int slack_command_connect_workspace(struct t_slack_workspace *workspace) int slack_command_workspace_connect(int argc, char **argv) { - int i, nb_connect, connect_ok; - struct t_slack_workspace *ptr_workspace; + int i, nb_connect, connect_ok; + struct t_slack_workspace *ptr_workspace; (void) argc; (void) argv; @@ -223,17 +228,17 @@ int slack_command_workspace_connect(int argc, char **argv) connect_ok = 1; nb_connect = 0; - for (i = 2; i < argc; i++) - { + for (i = 2; i < argc; i++) + { nb_connect++; - ptr_workspace = slack_workspace_search(argv[i]); - if (ptr_workspace) - { + ptr_workspace = slack_workspace_search(argv[i]); + if (ptr_workspace) + { if (!slack_command_connect_workspace(ptr_workspace)) { connect_ok = 0; } - } + } else { weechat_printf( @@ -243,7 +248,7 @@ int slack_command_workspace_connect(int argc, char **argv) weechat_prefix("error"), SLACK_PLUGIN_NAME, argv[i]); } - } + } return (connect_ok) ? WEECHAT_RC_OK : WEECHAT_RC_ERROR; } @@ -307,11 +312,11 @@ 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 || weechat_strcasecmp(argv[1], "list") == 0) + { + slack_command_workspace_list(argc, argv); + return WEECHAT_RC_OK; + } if (argc > 1) { @@ -339,6 +344,68 @@ int slack_command_slack(const void *pointer, void *data, return WEECHAT_RC_OK; } +int slack_command_me(const void *pointer, void *data, + struct t_gui_buffer *buffer, int argc, + char **argv, char **argv_eol) +{ + struct t_slack_workspace *ptr_workspace = NULL; + struct t_slack_channel *ptr_channel = NULL; + struct t_slack_request *request; + char *text; + + /* make C compiler happy */ + (void) pointer; + (void) data; + (void) buffer; + (void) argv; + + slack_buffer_get_workspace_and_channel(buffer, &ptr_workspace, &ptr_channel); + + if (!ptr_workspace) + return WEECHAT_RC_ERROR; + + if (!ptr_channel) + { + weechat_printf ( + ptr_workspace->buffer, + _("%s%s: \"%s\" command can not be executed on a workspace buffer"), + weechat_prefix("error"), SLACK_PLUGIN_NAME, "me"); + return WEECHAT_RC_OK; + } + + if (!ptr_workspace->is_connected) + { + weechat_printf(buffer, + _("%s%s: you are not connected to server"), + weechat_prefix("error"), SLACK_PLUGIN_NAME); + return WEECHAT_RC_OK; + } + + if (argc > 1) + { + 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, argv_eol[1], SLACK_MESSAGE_MAX_LENGTH); + + request = slack_request_chat_memessage(ptr_workspace, + weechat_config_string( + ptr_workspace->options[SLACK_WORKSPACE_OPTION_TOKEN]), + ptr_channel->id, text); + if (request) + slack_workspace_register_request(ptr_workspace, request); + + free(text); + } + + return WEECHAT_RC_OK; +} + void slack_command_init() { weechat_hook_command( @@ -357,4 +424,11 @@ void slack_command_init() " || connect %(slack_workspace)" " || delete %(slack_workspace)", &slack_command_slack, NULL, NULL); + + weechat_hook_command( + "me", + N_("send a slack action to the current channel"), + N_(""), + N_("message: message to send"), + NULL, &slack_command_me, NULL, NULL); }