summaryrefslogtreecommitdiff
path: root/src/core/chat.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/chat.c')
-rw-r--r--src/core/chat.c463
1 files changed, 463 insertions, 0 deletions
diff --git a/src/core/chat.c b/src/core/chat.c
new file mode 100644
index 0000000..fdd4fa4
--- /dev/null
+++ b/src/core/chat.c
@@ -0,0 +1,463 @@
+#include "hybbx/chat.h"
+#include "hybbx/service.h"
+#include "hybbx/session.h"
+#include "hybbx/config.h"
+#include "hybbx/util.h"
+#include "hybbx/traffic.h"
+#include "hybbx/log.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int str_ieq(const char *a, const char *b)
+{
+ if (a == NULL || b == NULL) {
+ return 0;
+ }
+
+ while (*a != '\0' && *b != '\0') {
+ char ca = (char)(*a >= 'A' && *a <= 'Z' ? *a + 32 : *a);
+ char cb = (char)(*b >= 'A' && *b <= 'Z' ? *b + 32 : *b);
+
+ if (ca != cb) {
+ return 0;
+ }
+ a++;
+ b++;
+ }
+
+ return *a == '\0' && *b == '\0';
+}
+
+static int channel_name_char_ok(char ch)
+{
+ return (ch >= 'a' && ch <= 'z') ||
+ (ch >= 'A' && ch <= 'Z') ||
+ (ch >= '0' && ch <= '9') ||
+ ch == ' ' || ch == '-' || ch == '_';
+}
+
+static int channel_name_valid(const char *name)
+{
+ size_t len;
+ size_t i;
+
+ if (name == NULL) {
+ return 0;
+ }
+
+ len = strlen(name);
+ if (len < 2 || len >= HYBBX_CHAT_CHANNEL_NAME_MAX) {
+ return 0;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (!channel_name_char_ok(name[i])) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+void hybbx_chat_config_defaults(hybbx_chat_config_t *chat)
+{
+ unsigned i;
+
+ if (chat == NULL) {
+ return;
+ }
+
+ chat->channel_count = HYBBX_DEFAULT_CHAT_CHANNELS;
+ chat->message_max = HYBBX_CHAT_MESSAGE_MAX;
+
+ for (i = 0; i < HYBBX_CHAT_CHANNEL_MAX; i++) {
+ snprintf(chat->names[i], sizeof(chat->names[i]), "Channel%u", i + 1);
+ }
+}
+
+void hybbx_chat_config_apply(hybbx_chat_config_t *chat,
+ const hybbx_config_t *config)
+{
+ unsigned count;
+ unsigned i;
+ char key[16];
+ const char *value;
+
+ if (chat == NULL || config == NULL) {
+ return;
+ }
+
+ hybbx_chat_config_defaults(chat);
+
+ count = hybbx_config_get_uint(config, "chat", "channels",
+ HYBBX_DEFAULT_CHAT_CHANNELS, 1u,
+ HYBBX_CHAT_CHANNEL_MAX);
+ chat->channel_count = count;
+
+ chat->message_max = hybbx_config_get_uint(config, "chat", "message_max",
+ HYBBX_CHAT_MESSAGE_MAX, 1u,
+ HYBBX_LINE_MAX - 1u);
+
+ for (i = 1; i <= chat->channel_count; i++) {
+ snprintf(key, sizeof(key), "channel%u", i);
+ value = hybbx_config_get(config, "chat", key, NULL);
+ if (value != NULL && value[0] != '\0' && channel_name_valid(value)) {
+ hybbx_strlcpy(chat->names[i - 1], value,
+ sizeof(chat->names[i - 1]));
+ }
+ }
+
+ hybbx_log_info("[chat] channels=%u message_max=%u",
+ chat->channel_count, chat->message_max);
+}
+
+const char *hybbx_chat_channel_name(const hybbx_chat_config_t *chat,
+ unsigned channel_index)
+{
+ if (chat == NULL || channel_index == 0 ||
+ channel_index > chat->channel_count) {
+ return NULL;
+ }
+
+ return chat->names[channel_index - 1];
+}
+
+hybbx_result_t hybbx_chat_resolve_channel(const hybbx_chat_config_t *chat,
+ const char *spec,
+ unsigned *out_index)
+{
+ char *end;
+ unsigned long parsed;
+ unsigned i;
+
+ if (chat == NULL || spec == NULL || out_index == NULL || spec[0] == '\0') {
+ return HYBBX_ERR_INVALID;
+ }
+
+ parsed = strtoul(spec, &end, 10);
+ if (end != spec && *end == '\0' && parsed >= 1 &&
+ parsed <= chat->channel_count) {
+ *out_index = (unsigned)parsed;
+ return HYBBX_OK;
+ }
+
+ for (i = 1; i <= chat->channel_count; i++) {
+ if (str_ieq(chat->names[i - 1], spec)) {
+ *out_index = i;
+ return HYBBX_OK;
+ }
+ }
+
+ return HYBBX_ERR_NOT_FOUND;
+}
+
+void hybbx_chat_list_channels(hybbx_session_t *session,
+ const hybbx_chat_config_t *chat)
+{
+ unsigned i;
+ char line[HYBBX_CHAT_CHANNEL_NAME_MAX + 32];
+ unsigned current;
+
+ if (session == NULL || chat == NULL) {
+ return;
+ }
+
+ current = hybbx_session_chat_channel(session);
+
+ hybbx_session_write_line(session, "Channels:");
+ for (i = 1; i <= chat->channel_count; i++) {
+ snprintf(line, sizeof(line), " %u %s", i, chat->names[i - 1]);
+ hybbx_session_write_line(session, line);
+ }
+
+ if (current > 0) {
+ snprintf(line, sizeof(line), "Current: %u %s",
+ current, hybbx_chat_channel_name(chat, current));
+ hybbx_session_write_line(session, line);
+ } else {
+ hybbx_session_write_line(session, "Use /chat <n> or <name>.");
+ }
+}
+
+typedef struct chat_broadcast_ctx {
+ hybbx_session_t *from;
+ const char *message;
+ const char *from_user;
+ unsigned channel;
+} chat_broadcast_ctx_t;
+
+static void chat_broadcast_visitor(hybbx_session_t *session, void *userdata)
+{
+ chat_broadcast_ctx_t *ctx = (chat_broadcast_ctx_t *)userdata;
+ char line[HYBBX_USER_NAME_MAX + HYBBX_LINE_MAX + 16];
+
+ if (session == NULL || ctx == NULL || ctx->message == NULL) {
+ return;
+ }
+
+ if (hybbx_session_area(session) != HYBBX_AREA_CHAT) {
+ return;
+ }
+
+ if (hybbx_session_chat_channel(session) != ctx->channel) {
+ return;
+ }
+
+ if (session == ctx->from) {
+ snprintf(line, sizeof(line), "ME: %s", ctx->message);
+ } else {
+ snprintf(line, sizeof(line), "%s: %s", ctx->from_user, ctx->message);
+ }
+
+ hybbx_session_write_line(session, line);
+}
+
+hybbx_result_t hybbx_chat_post(hybbx_service_t *service,
+ hybbx_session_t *from,
+ const char *message)
+{
+ chat_broadcast_ctx_t ctx;
+ const hybbx_chat_config_t *chat;
+ unsigned channel;
+
+ if (service == NULL || from == NULL || message == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ if (message[0] == '\0') {
+ return HYBBX_OK;
+ }
+
+ chat = hybbx_service_get_chat(service);
+ if (chat == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ if (strlen(message) > chat->message_max) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ if (hybbx_session_area(from) != HYBBX_AREA_CHAT) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ channel = hybbx_session_chat_channel(from);
+ if (channel == 0) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ ctx.from = from;
+ ctx.message = message;
+ ctx.from_user = hybbx_session_display_name(from);
+ ctx.channel = channel;
+
+ hybbx_service_visit_sessions(service, chat_broadcast_visitor, &ctx);
+ return HYBBX_OK;
+}
+
+#define CHAT_SHOWALL_MAX 128u
+
+typedef struct chat_show_entry {
+ unsigned channel;
+ char label[HYBBX_USER_NAME_MAX + HYBBX_CHAT_CHANNEL_NAME_MAX + 4];
+} chat_show_entry_t;
+
+typedef struct chat_show_ctx {
+ hybbx_session_t *requester;
+ unsigned channel;
+ unsigned count;
+ chat_show_entry_t entries[CHAT_SHOWALL_MAX];
+ const hybbx_chat_config_t *chat;
+} chat_show_ctx_t;
+
+static void chat_show_channel_visitor(hybbx_session_t *session, void *userdata)
+{
+ chat_show_ctx_t *ctx = (chat_show_ctx_t *)userdata;
+
+ if (session == NULL || ctx == NULL || session == ctx->requester) {
+ return;
+ }
+
+ if (hybbx_session_area(session) != HYBBX_AREA_CHAT) {
+ return;
+ }
+
+ if (hybbx_session_chat_channel(session) != ctx->channel) {
+ return;
+ }
+
+ if (hybbx_session_is_guest(session)) {
+ return;
+ }
+
+ if (hybbx_session_hidden_from_who(session)) {
+ return;
+ }
+
+ hybbx_session_write_line(ctx->requester, hybbx_session_display_name(session));
+}
+
+void hybbx_chat_show_channel(hybbx_service_t *service, hybbx_session_t *session)
+{
+ chat_show_ctx_t ctx;
+ unsigned channel;
+
+ if (service == NULL || session == NULL) {
+ return;
+ }
+
+ if (hybbx_session_area(session) != HYBBX_AREA_CHAT) {
+ hybbx_session_write_line(session, "Not in a chat channel.");
+ return;
+ }
+
+ channel = hybbx_session_chat_channel(session);
+ if (channel == 0) {
+ hybbx_session_write_line(session, "Not in a chat channel.");
+ return;
+ }
+
+ ctx.requester = session;
+ ctx.channel = channel;
+ ctx.count = 0;
+ ctx.chat = NULL;
+
+ hybbx_service_visit_sessions(service, chat_show_channel_visitor, &ctx);
+}
+
+static void chat_show_all_collect(hybbx_session_t *session, void *userdata)
+{
+ chat_show_ctx_t *ctx = (chat_show_ctx_t *)userdata;
+ unsigned channel;
+ const char *ch_name;
+ chat_show_entry_t *entry;
+
+ if (session == NULL || ctx == NULL || ctx->chat == NULL) {
+ return;
+ }
+
+ if (hybbx_session_area(session) != HYBBX_AREA_CHAT) {
+ return;
+ }
+
+ if (hybbx_session_is_guest(session)) {
+ return;
+ }
+
+ if (hybbx_session_hidden_from_who(session)) {
+ return;
+ }
+
+ channel = hybbx_session_chat_channel(session);
+ if (channel == 0 || channel > ctx->chat->channel_count) {
+ return;
+ }
+
+ if (ctx->count >= CHAT_SHOWALL_MAX) {
+ return;
+ }
+
+ ch_name = hybbx_chat_channel_name(ctx->chat, channel);
+ if (ch_name == NULL) {
+ return;
+ }
+
+ entry = &ctx->entries[ctx->count++];
+ entry->channel = channel;
+ snprintf(entry->label, sizeof(entry->label), "%s@%s",
+ hybbx_session_display_name(session), ch_name);
+}
+
+static int chat_show_entry_cmp(const void *a, const void *b)
+{
+ const chat_show_entry_t *ea = (const chat_show_entry_t *)a;
+ const chat_show_entry_t *eb = (const chat_show_entry_t *)b;
+
+ if (ea->channel != eb->channel) {
+ return (ea->channel < eb->channel) ? -1 : 1;
+ }
+
+ return strcmp(ea->label, eb->label);
+}
+
+static void chat_showall_flush(hybbx_session_t *session, char *line, size_t *pos)
+{
+ if (session == NULL || line == NULL || pos == NULL || *pos == 0) {
+ return;
+ }
+
+ line[*pos] = '\0';
+ hybbx_session_write_line(session, line);
+ line[0] = '\0';
+ *pos = 0;
+}
+
+static void chat_showall_append(hybbx_session_t *session, char *line,
+ size_t line_size, size_t *pos,
+ const char *entry)
+{
+ size_t entry_len;
+ size_t need;
+
+ if (session == NULL || line == NULL || pos == NULL || entry == NULL) {
+ return;
+ }
+
+ entry_len = strlen(entry);
+ need = (*pos > 0) ? (*pos + 1 + entry_len) : entry_len;
+
+ if (*pos > 0 && need > HYBBX_LINE_WIDTH) {
+ chat_showall_flush(session, line, pos);
+ need = entry_len;
+ }
+
+ if (entry_len >= line_size) {
+ return;
+ }
+
+ if (*pos > 0) {
+ line[(*pos)++] = ' ';
+ }
+
+ memcpy(line + *pos, entry, entry_len);
+ *pos += entry_len;
+ line[*pos] = '\0';
+}
+
+void hybbx_chat_show_all(hybbx_service_t *service, hybbx_session_t *session)
+{
+ chat_show_ctx_t ctx;
+ char line[HYBBX_LINE_WIDTH + 1];
+ size_t pos = 0;
+ unsigned i;
+
+ if (service == NULL || session == NULL) {
+ return;
+ }
+
+ ctx.requester = session;
+ ctx.channel = 0;
+ ctx.count = 0;
+ ctx.chat = hybbx_service_get_chat(service);
+ if (ctx.chat == NULL) {
+ return;
+ }
+
+ hybbx_service_visit_sessions(service, chat_show_all_collect, &ctx);
+
+ if (ctx.count == 0) {
+ return;
+ }
+
+ qsort(ctx.entries, ctx.count, sizeof(ctx.entries[0]), chat_show_entry_cmp);
+
+ line[0] = '\0';
+ for (i = 0; i < ctx.count; i++) {
+ chat_showall_append(session, line, sizeof(line), &pos, ctx.entries[i].label);
+ }
+
+ chat_showall_flush(session, line, &pos);
+}
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com