typing notifications

This commit is contained in:
Tony Olagbaiye 2018-05-06 01:09:58 +01:00
parent 22f22a0aee
commit 0c575b9dbc
19 changed files with 478 additions and 74 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
# Prerequisites
compile_commands.json
cscope*
.depend
*.d

View File

@ -1,8 +1,10 @@
CC=clang
CXX=clang++
CXX=g++
RM=rm -f
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
SANCFLAGS=-fsanitize=address -fsanitize=leak
SANLDFLAGS=-static-libasan -static-liblsan
CFLAGS=$(SANCFLAGS) -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 $(SANCFLAGS) $(SANLDFLAGS)
LDLIBS=-lgnutls
SRCS=slack.c \
@ -21,6 +23,7 @@ SRCS=slack.c \
api/slack-api-hello.c \
api/slack-api-error.c \
api/slack-api-message.c \
api/slack-api-user-typing.c \
api/message/slack-api-message-unimplemented.c \
request/slack-request-chat-postmessage.c \
request/slack-request-channels-list.c \
@ -30,7 +33,7 @@ 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)
$(CC) $(LDFLAGS) -o slack.so $(OBJS) $(LDLIBS)
$(CXX) $(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 -DCMAKE_BUILD_TYPE=DEBUG .

View File

@ -82,6 +82,7 @@ int slack_api_message_message_handle(struct t_slack_workspace *workspace,
{
struct t_slack_channel *ptr_channel;
struct t_slack_user *ptr_user;
struct t_slack_channel_typing *ptr_typing;
ptr_channel = slack_channel_search(workspace, channel);
if (!ptr_channel)
@ -100,6 +101,14 @@ int slack_api_message_message_handle(struct t_slack_workspace *workspace,
message);
free(message);
ptr_typing = slack_channel_typing_search(ptr_channel,
ptr_user->profile.display_name);
if (ptr_typing)
{
slack_channel_typing_free(ptr_channel, ptr_typing);
slack_channel_typing_cb(ptr_channel, NULL, 0);
}
return 1;
}

View File

@ -0,0 +1,63 @@
#include <json.h>
#include "../weechat-plugin.h"
#include "../slack.h"
#include "../slack-workspace.h"
#include "../slack-user.h"
#include "../slack-channel.h"
#include "../slack-api.h"
#include "slack-api-user-typing.h"
static const char *type = "user_typing";
static inline int json_valid(json_object *object, struct t_slack_workspace *workspace)
{
if (!object)
{
weechat_printf(
workspace->buffer,
_("%s%s: error handling websocket %s%s%s message: "
"unexpected response from server"),
weechat_prefix("error"), SLACK_PLUGIN_NAME,
weechat_color("chat_value"), type, weechat_color("reset"));
return 0;
}
return 1;
}
int slack_api_user_typing_handle(struct t_slack_workspace *workspace,
const char *channel, const char *user)
{
struct t_slack_channel *ptr_channel;
struct t_slack_user *ptr_user;
ptr_channel = slack_channel_search(workspace, channel);
if (!ptr_channel)
return 1; /* silently ignore if channel hasn't been loaded yet */
ptr_user = slack_user_search(workspace, user);
if (!ptr_user)
return 1; /* silently ignore if user hasn't been loaded yet */
slack_channel_add_typing(ptr_channel, ptr_user);
return 1;
}
int slack_api_user_typing(struct t_slack_workspace *workspace,
json_object *message)
{
json_object *channel, *user;
channel = json_object_object_get(message, "channel");
if (!json_valid(channel, workspace))
return 0;
user = json_object_object_get(message, "user");
if (!json_valid(user, workspace))
return 0;
return slack_api_user_typing_handle(workspace,
json_object_get_string(channel),
json_object_get_string(user));
}

View File

@ -0,0 +1,7 @@
#ifndef _SLACK_API_USER_TYPING_H_
#define _SLACK_API_USER_TYPING_H_
int slack_api_user_typing(struct t_slack_workspace *workspace,
json_object *message);
#endif /*SLACK_API_USER_TYPING_H*/

View File

@ -245,6 +245,7 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason,
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?

View File

@ -165,6 +165,7 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason,
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?

View File

@ -251,6 +251,7 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason,
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?

View File

@ -10,6 +10,7 @@
#include "api/slack-api-hello.h"
#include "api/slack-api-error.h"
#include "api/slack-api-message.h"
#include "api/slack-api-user-typing.h"
struct stringcase
{
@ -22,6 +23,7 @@ static struct stringcase cases[] =
{ { "hello", &slack_api_hello }
, { "error", &slack_api_error }
, { "message", &slack_api_message }
, { "user_typing", &slack_api_user_typing }
};
static int stringcase_cmp(const void *p1, const void *p2)
@ -71,12 +73,53 @@ static int callback_ws(struct lws* wsi, enum lws_callback_reasons reason,
weechat_prefix("network"), SLACK_PLUGIN_NAME,
(const char *)in);
{
int data_size;
char *json_string;
json_object *response, *type;
struct t_json_chunk *new_chunk, *last_chunk, *chunk_ptr;
json_string = strdup((const char *)in);
new_chunk = malloc(sizeof(*new_chunk));
new_chunk->data = malloc(((int)len) + 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;
}
data_size = 0;
for (chunk_ptr = workspace->json_chunks; chunk_ptr; chunk_ptr = chunk_ptr->next)
{
data_size += strlen(chunk_ptr->data);
}
json_string = malloc(data_size + 1);
json_string[0] = '\0';
for (chunk_ptr = workspace->json_chunks; chunk_ptr; chunk_ptr = chunk_ptr->next)
{
strcat(json_string, chunk_ptr->data);
}
response = json_tokener_parse(json_string);
if (response)
{
for (chunk_ptr = workspace->json_chunks; chunk_ptr; workspace->json_chunks = chunk_ptr)
{
chunk_ptr = chunk_ptr->next;
free(workspace->json_chunks->data);
free(workspace->json_chunks);
}
type = json_object_object_get(response, "type");
if (!type)
{
@ -115,6 +158,11 @@ static int callback_ws(struct lws* wsi, enum lws_callback_reasons reason,
json_object_put(response);
free(json_string);
}
else
{
free(json_string);
}
}
return 0; /* don't passthru */
case LWS_CALLBACK_CLIENT_WRITEABLE:

View File

@ -1,3 +1,5 @@
#include <string.h>
#include "weechat-plugin.h"
#include "slack.h"
#include "slack-workspace.h"
@ -42,6 +44,67 @@ void slack_buffer_get_workspace_and_channel(struct t_gui_buffer *buffer,
/* no workspace or channel found */
}
char *slack_buffer_typing_bar_cb(const void *pointer,
void *data,
struct t_gui_bar_item *item,
struct t_gui_window *window,
struct t_gui_buffer *buffer,
struct t_hashtable *extra_info)
{
struct t_slack_channel_typing *ptr_typing;
struct t_slack_workspace *workspace;
struct t_slack_channel *channel;
char notification[256];
unsigned typecount;
(void) pointer;
(void) data;
(void) item;
(void) window;
(void) extra_info;
workspace = NULL;
channel = NULL;
slack_buffer_get_workspace_and_channel(buffer, &workspace, &channel);
if (!channel)
return strdup("");
typecount = 0;
for (ptr_typing = channel->typings; ptr_typing;
ptr_typing = ptr_typing->next_typing)
{
switch (++typecount)
{
case 1:
strcpy(notification, ptr_typing->name);
break;
case 2:
strcat(notification, ", ");
strcat(notification, ptr_typing->name);
break;
case 3:
default:
strcpy(notification, "Several people");
break;
}
}
if (typecount)
{
strcat(notification, NG_(" is typing...",
" are typing...",
typecount));
return strdup(notification);
}
else
{
return strdup("");
}
}
int slack_buffer_nickcmp_cb(const void *pointer, void *data,
struct t_gui_buffer *buffer,
const char *nick1,

View File

@ -5,6 +5,13 @@ void slack_buffer_get_workspace_and_channel(struct t_gui_buffer *buffer,
struct t_slack_workspace **workspace,
struct t_slack_channel **channel);
char *slack_buffer_typing_bar_cb(const void *pointer,
void *data,
struct t_gui_bar_item *item,
struct t_gui_window *window,
struct t_gui_buffer *buffer,
struct t_hashtable *extra_info);
int slack_buffer_nickcmp_cb(const void *pointer, void *data,
struct t_gui_buffer *buffer,
const char *nick1,

View File

@ -1,10 +1,12 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "weechat-plugin.h"
#include "slack.h"
#include "slack-workspace.h"
#include "slack-user.h"
#include "slack-channel.h"
#include "slack-input.h"
#include "slack-buffer.h"
@ -73,7 +75,7 @@ struct t_gui_buffer *slack_channel_create_buffer(struct t_slack_workspace *works
const char *name)
{
struct t_gui_buffer *ptr_buffer;
int buffer_created, current_buffer_number;
int buffer_created;
const char *short_name, *localvar_channel;
char buffer_name[256];
@ -89,9 +91,6 @@ struct t_gui_buffer *slack_channel_create_buffer(struct t_slack_workspace *works
}
else
{
current_buffer_number = weechat_buffer_get_integer(
weechat_current_buffer(), "number");
ptr_buffer = weechat_buffer_new(buffer_name,
&slack_input_data_cb, NULL, NULL,
&slack_buffer_close_cb, NULL, NULL);
@ -181,6 +180,7 @@ struct t_slack_channel *slack_channel_new(struct t_slack_workspace *workspace,
{
struct t_slack_channel *new_channel, *ptr_channel;
struct t_gui_buffer *ptr_buffer;
struct t_hook *typing_timer;
char buffer_name[SLACK_CHANNEL_NAME_MAX_LEN + 2];
if (!workspace || !id || !name || !name[0])
@ -202,6 +202,10 @@ struct t_slack_channel *slack_channel_new(struct t_slack_workspace *workspace,
if ((new_channel = malloc(sizeof(*new_channel))) == NULL)
return NULL;
typing_timer = weechat_hook_timer(1 * 1000, 0, 0,
&slack_channel_typing_cb,
new_channel, NULL);
new_channel->type = type;
new_channel->id = strdup(id);
new_channel->name = strdup(name);
@ -228,6 +232,9 @@ struct t_slack_channel *slack_channel_new(struct t_slack_workspace *workspace,
new_channel->is_user_deleted = 0;
new_channel->typing_hook_timer = typing_timer;
new_channel->typings = NULL;
new_channel->last_typing = NULL;
new_channel->members = NULL;
new_channel->last_member = NULL;
new_channel->buffer = ptr_buffer;
@ -244,6 +251,137 @@ struct t_slack_channel *slack_channel_new(struct t_slack_workspace *workspace,
return new_channel;
}
void slack_channel_typing_free(struct t_slack_channel *channel,
struct t_slack_channel_typing *typing)
{
struct t_slack_channel_typing *new_typings;
if (!channel || !typing)
return;
/* remove typing from typings list */
if (channel->last_typing == typing)
channel->last_typing = typing->prev_typing;
if (typing->prev_typing)
{
(typing->prev_typing)->next_typing = typing->next_typing;
new_typings = channel->typings;
}
else
new_typings = typing->next_typing;
if (typing->next_typing)
(typing->next_typing)->prev_typing = typing->prev_typing;
/* free typing data */
if (typing->id)
free(typing->id);
if (typing->name)
free(typing->name);
free(typing);
channel->typings = new_typings;
}
void slack_channel_typing_free_all(struct t_slack_channel *channel)
{
while (channel->typings)
slack_channel_typing_free(channel, channel->typings);
}
int slack_channel_typing_cb(const void *pointer,
void *data,
int remaining_calls)
{
struct t_slack_channel_typing *ptr_typing, *next_typing;
struct t_slack_channel *channel;
unsigned typecount;
time_t now;
(void) data;
(void) remaining_calls;
if (!pointer)
return WEECHAT_RC_ERROR;
channel = (struct t_slack_channel *)pointer;
now = time(NULL);
typecount = 0;
for (ptr_typing = channel->typings; ptr_typing;
ptr_typing = ptr_typing->next_typing)
{
next_typing = ptr_typing->next_typing;
while (ptr_typing && now - ptr_typing->ts > 5)
{
slack_channel_typing_free(channel, ptr_typing);
ptr_typing = next_typing;
if (ptr_typing)
next_typing = ptr_typing->next_typing;
}
if (!ptr_typing)
break;
typecount++;
}
weechat_buffer_set(channel->buffer,
"localvar_set_typing",
typecount > 0 ? "1" : "0");
weechat_bar_item_update("slack_typing");
return WEECHAT_RC_OK;
}
struct t_slack_channel_typing *slack_channel_typing_search(
struct t_slack_channel *channel,
const char *id)
{
struct t_slack_channel_typing *ptr_typing;
if (!channel || !id)
return NULL;
for (ptr_typing = channel->typings; ptr_typing;
ptr_typing = ptr_typing->next_typing)
{
if (weechat_strcasecmp(ptr_typing->id, id) == 0)
return ptr_typing;
}
return NULL;
}
void slack_channel_add_typing(struct t_slack_channel *channel,
struct t_slack_user *user)
{
struct t_slack_channel_typing *new_typing;
new_typing = slack_channel_typing_search(channel, user->id);
if (!new_typing)
{
new_typing = malloc(sizeof(*new_typing));
new_typing->id = strdup(user->id);
new_typing->name = strdup(user->profile.display_name);
new_typing->prev_typing = channel->last_typing;
new_typing->next_typing = NULL;
if (channel->last_typing)
(channel->last_typing)->next_typing = new_typing;
else
channel->typings = new_typing;
channel->last_typing = new_typing;
}
new_typing->ts = time(NULL);
slack_channel_typing_cb(channel, NULL, 0);
}
void slack_channel_member_free(struct t_slack_channel *channel,
struct t_slack_channel_member *member)
{
@ -303,7 +441,12 @@ void slack_channel_free(struct t_slack_workspace *workspace,
if (channel->next_channel)
(channel->next_channel)->prev_channel = channel->prev_channel;
/* free hooks */
if (channel->typing_hook_timer)
weechat_unhook(channel->typing_hook_timer);
/* free linked lists */
slack_channel_typing_free_all(channel);
slack_channel_member_free_all(channel);
/* free channel data */
@ -324,7 +467,7 @@ void slack_channel_free(struct t_slack_workspace *workspace,
if (channel->creator)
free(channel->creator);
if (channel->buffer_as_string)
free (channel->buffer_as_string);
free(channel->buffer_as_string);
free(channel);

View File

@ -11,6 +11,16 @@ enum t_slack_channel_type
SLACK_CHANNEL_TYPE_IM,
};
struct t_slack_channel_typing
{
char *id;
char *name;
time_t ts;
struct t_slack_channel_typing *prev_typing;
struct t_slack_channel_typing *next_typing;
};
struct t_slack_channel_member
{
char *id;
@ -61,6 +71,9 @@ struct t_slack_channel
/* im */
int is_user_deleted;
struct t_hook *typing_hook_timer;
struct t_slack_channel_typing *typings;
struct t_slack_channel_typing *last_typing;
struct t_slack_channel_member *members;
struct t_slack_channel_member *last_member;
struct t_gui_buffer *buffer;
@ -80,6 +93,22 @@ struct t_slack_channel *slack_channel_new(struct t_slack_workspace *workspace,
enum t_slack_channel_type type,
const char *id, const char *name);
void slack_channel_typing_free(struct t_slack_channel *channel,
struct t_slack_channel_typing *typing);
void slack_channel_typing_free_all(struct t_slack_channel *channel);
int slack_channel_typing_cb(const void *pointer,
void *data,
int remaining_calls);
struct t_slack_channel_typing *slack_channel_typing_search(
struct t_slack_channel *channel,
const char *id);
void slack_channel_add_typing(struct t_slack_channel *channel,
struct t_slack_user *user);
void slack_channel_free_all(struct t_slack_workspace *workspace);
#endif /*SLACK_CHANNEL_H*/

View File

@ -1,5 +1,6 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <regex.h>
#include "../weechat-plugin.h"
@ -9,7 +10,7 @@
#include "../slack-user.h"
#include "../slack-message.h"
static const char format_regex[] = "<(.*?)>";
static const char format_regex[] = "<([^>]*?)>";
static const size_t max_groups = 2;
char *slack_message_translate_code(struct t_slack_workspace *workspace,
@ -30,7 +31,12 @@ char *slack_message_translate_code(struct t_slack_workspace *workspace,
case '@': /* user */
user = slack_user_search(workspace, code+1);
if (user)
return strdup(user->profile.display_name);
{
size_t nicklen = snprintf(NULL, 0, "%s@%s%s", weechat_color("chat_nick"), user->profile.display_name, weechat_color("reset")) + 1;
char *tag = malloc(nicklen);
snprintf(tag, nicklen, "%s@%s%s", weechat_color("chat_nick"), user->profile.display_name, weechat_color("reset"));
return tag;
}
else
return strdup(code);
case '!': /* special */
@ -49,7 +55,7 @@ char *slack_message_decode(struct t_slack_workspace *workspace,
regex_t reg;
regmatch_t groups[max_groups];
char msgbuf[100];
char *decoded_text, *pos;
char *decoded_text;
const char *cursor;
size_t offset;
@ -74,8 +80,7 @@ char *slack_message_decode(struct t_slack_workspace *workspace,
weechat_prefix("error"), SLACK_PLUGIN_NAME);
return strdup(text);
}
pos = decoded_text;
pos[0] = '\0';
decoded_text[0] = '\0';
for (cursor = text; regexec(&reg, cursor, max_groups, groups, 0) == 0; cursor += offset)
{
@ -85,6 +90,7 @@ char *slack_message_decode(struct t_slack_workspace *workspace,
if (!copy)
{
regfree(&reg);
free(decoded_text);
weechat_printf(
workspace->buffer,
_("%s%s: error allocating space for message"),
@ -98,6 +104,7 @@ char *slack_message_decode(struct t_slack_workspace *workspace,
{
free(copy);
regfree(&reg);
free(decoded_text);
weechat_printf(
workspace->buffer,
_("%s%s: error allocating space for message"),
@ -112,6 +119,7 @@ char *slack_message_decode(struct t_slack_workspace *workspace,
free(match);
free(copy);
regfree(&reg);
free(decoded_text);
weechat_printf(
workspace->buffer,
_("%s%s: error allocating space for message"),
@ -120,18 +128,18 @@ char *slack_message_decode(struct t_slack_workspace *workspace,
}
free(copy);
pos = strncat(decoded_text, prematch,
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,
strncat(decoded_text, replacement,
SLACK_MESSAGE_MAX_LENGTH - strlen(decoded_text) - 1);
free(replacement);
}
pos = strncat(decoded_text, cursor,
strncat(decoded_text, cursor,
SLACK_MESSAGE_MAX_LENGTH - strlen(decoded_text) - 1);
regfree(&reg);

View File

@ -228,6 +228,7 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason,
json_object_put(response);
free(json_string);
}
/* fallthrough */
case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
client_wsi = NULL;
/* Does not doing this cause a leak?

View File

@ -247,6 +247,7 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason,
json_object_put(response);
free(json_string);
}
/* fallthrough */
case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
workspace->client_wsi = NULL;
/* Does not doing this cause a leak?

View File

@ -6,7 +6,7 @@ extern struct t_slack_workspace *last_slack_workspace;
enum t_slack_workspace_option
{
SLACK_WORKSPACE_OPTION_TOKEN = 0,
SLACK_WORKSPACE_OPTION_TOKEN,
SLACK_WORKSPACE_NUM_OPTIONS,
};

18
slack.c
View File

@ -10,6 +10,7 @@
#include "slack-command.h"
#include "slack-workspace.h"
#include "slack-api.h"
#include "slack-buffer.h"
WEECHAT_PLUGIN_NAME(SLACK_PLUGIN_NAME);
@ -23,6 +24,8 @@ struct t_weechat_plugin *weechat_slack_plugin = NULL;
struct t_hook *slack_hook_timer = NULL;
struct t_gui_bar_item *slack_typing_bar_item = NULL;
void slack_lwsl_emit_weechat(int level, const char *line)
{
char buf[50];
@ -60,6 +63,18 @@ int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char *argv[])
&slack_workspace_timer_cb,
NULL, NULL);
if (!weechat_bar_search("typing"))
{
weechat_bar_new("typing", "off", "400", "window", "${typing}",
"bottom", "horizontal", "vertical",
"1", "1", "default", "default", "default",
"off", "slack_typing");
}
slack_typing_bar_item = weechat_bar_item_new("slack_typing",
&slack_buffer_typing_bar_cb,
NULL, NULL);
return WEECHAT_RC_OK;
}
@ -68,6 +83,9 @@ int weechat_plugin_end(struct t_weechat_plugin *plugin)
/* make C compiler happy */
(void) plugin;
if (slack_typing_bar_item)
weechat_bar_item_remove(slack_typing_bar_item);
if (slack_hook_timer)
weechat_unhook(slack_hook_timer);