From 20cb29c2f8c5c87bc590896854a20b1473ceb358 Mon Sep 17 00:00:00 2001 From: "info@mode42.com" Date: Sat, 8 Aug 2026 03:54:55 +0000 Subject: #2 --- src/core/conference.c | 720 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 720 insertions(+) create mode 100644 src/core/conference.c (limited to 'src/core/conference.c') diff --git a/src/core/conference.c b/src/core/conference.c new file mode 100644 index 0000000..890348f --- /dev/null +++ b/src/core/conference.c @@ -0,0 +1,720 @@ +#include "hybbx/conference.h" +#include "hybbx/chat.h" +#include "hybbx/messages.h" +#include "hybbx/service.h" +#include "hybbx/session.h" +#include "hybbx/storage.h" +#include "hybbx/auth.h" +#include "hybbx/monitor.h" +#include "hybbx/traffic.h" +#include "hybbx/util.h" + +#include +#include +#include + +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 invite_reply_yes(const char *line) +{ + return line != NULL && + (str_ieq(line, "y") || str_ieq(line, "yes")); +} + +static int invite_reply_no(const char *line) +{ + return line != NULL && + (str_ieq(line, "n") || str_ieq(line, "no")); +} + +static int topic_char_ok(char ch) +{ + return (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + ch == ' ' || ch == '-' || ch == '_'; +} + +static int conference_topic_valid(const char *topic) +{ + size_t len; + size_t i; + + if (topic == NULL) { + return 0; + } + + len = strlen(topic); + if (len < 2 || len >= HYBBX_CONFERENCE_TOPIC_MAX) { + return 0; + } + + for (i = 0; i < len; i++) { + if (!topic_char_ok(topic[i])) { + return 0; + } + } + + return 1; +} + +typedef struct find_user_session_ctx { + const char *username; + hybbx_session_t *found; +} find_user_session_ctx_t; + +static void find_user_session_visitor(hybbx_session_t *session, void *userdata) +{ + find_user_session_ctx_t *ctx = (find_user_session_ctx_t *)userdata; + + if (ctx == NULL || session == NULL || ctx->found != NULL) { + return; + } + + if (!hybbx_session_logged_in(session) || hybbx_session_is_guest(session)) { + return; + } + + if (str_ieq(hybbx_session_username(session), ctx->username)) { + ctx->found = session; + } +} + +static hybbx_session_t *conference_find_online_user(hybbx_service_t *service, + const char *username) +{ + find_user_session_ctx_t ctx; + + if (service == NULL || username == NULL || username[0] == '\0') { + return NULL; + } + + ctx.username = username; + ctx.found = NULL; + hybbx_service_visit_sessions(service, find_user_session_visitor, &ctx); + return ctx.found; +} + +static int conference_session_active(const hybbx_session_t *session) +{ + return session != NULL && + hybbx_session_area(session) == HYBBX_AREA_CONFERENCE; +} + +typedef struct invite_cancel_ctx { + const char *from_username; +} invite_cancel_ctx_t; + +static void invite_cancel_visitor(hybbx_session_t *session, void *userdata) +{ + invite_cancel_ctx_t *ctx = (invite_cancel_ctx_t *)userdata; + + if (session == NULL || ctx == NULL || ctx->from_username == NULL) { + return; + } + + if (!hybbx_conference_invite_pending(session)) { + return; + } + + if (!str_ieq(hybbx_session_conference_invite_from(session), + ctx->from_username)) { + return; + } + + hybbx_session_clear_conference_invite(session); + hybbx_session_write_line(session, "*** Conference invite cancelled."); +} + +static void conference_cancel_invites_from(hybbx_service_t *service, + const char *from_username) +{ + invite_cancel_ctx_t ctx; + + if (service == NULL || from_username == NULL || from_username[0] == '\0') { + return; + } + + ctx.from_username = from_username; + hybbx_service_visit_sessions(service, invite_cancel_visitor, &ctx); +} + +static void conference_notify_partner_left(hybbx_session_t *session, + const char *partner_username) +{ + hybbx_service_t *service; + hybbx_session_t *partner; + char line[HYBBX_LINE_MAX]; + + if (session == NULL || partner_username == NULL || + partner_username[0] == '\0') { + return; + } + + service = hybbx_session_service(session); + if (service == NULL) { + return; + } + + partner = conference_find_online_user(service, partner_username); + if (partner == NULL || !conference_session_active(partner)) { + return; + } + + if (!str_ieq(hybbx_session_conference_partner(partner), + hybbx_session_username(session))) { + return; + } + + hybbx_session_clear_conference(partner); + snprintf(line, sizeof(line), "*** %s left the conference.", + hybbx_session_display_name(session)); + hybbx_session_write_line(partner, line); + if (hybbx_session_area(partner) == HYBBX_AREA_CONFERENCE) { + (void)hybbx_session_leave_area(partner); + } +} + +static hybbx_result_t conference_activate(hybbx_service_t *service, + hybbx_session_t *initiator, + hybbx_session_t *partner, + const char *topic) +{ + char line[HYBBX_LINE_MAX]; + hybbx_result_t rc; + + if (service == NULL || initiator == NULL || partner == NULL || + topic == NULL) { + return HYBBX_ERR_INVALID; + } + + rc = hybbx_session_join_conference(initiator, topic, + hybbx_session_username(partner)); + if (rc != HYBBX_OK) { + return rc; + } + + rc = hybbx_session_join_conference(partner, topic, + hybbx_session_username(initiator)); + if (rc != HYBBX_OK) { + hybbx_session_clear_conference(initiator); + (void)hybbx_session_leave_area(initiator); + return rc; + } + + snprintf(line, sizeof(line), "Conference: %s", topic); + hybbx_session_write_line(initiator, line); + hybbx_session_write_line(initiator, + "Each line is a message; /leave or /main to exit."); + + snprintf(line, sizeof(line), "Conference: %s", topic); + hybbx_session_write_line(partner, line); + hybbx_session_write_line(partner, + "Each line is a message; /leave or /main to exit."); + + return HYBBX_OK; +} + +hybbx_result_t hybbx_conference_start(hybbx_service_t *service, + hybbx_session_t *initiator, + const char *topic, + const char *partner_spec) +{ + hybbx_storage_t *storage; + hybbx_user_record_t partner_user; + hybbx_session_t *partner_session; + char line[HYBBX_LINE_MAX]; + hybbx_result_t rc; + + if (service == NULL || initiator == NULL || topic == NULL || + partner_spec == NULL || topic[0] == '\0' || partner_spec[0] == '\0') { + return HYBBX_ERR_INVALID; + } + + if (hybbx_session_is_guest(initiator)) { + hybbx_session_write_line(initiator, "Guests cannot use conference."); + return HYBBX_ERR_DENIED; + } + + if (!conference_topic_valid(topic)) { + hybbx_session_write_line(initiator, "Invalid conference topic."); + return HYBBX_ERR_INVALID; + } + + if (conference_session_active(initiator)) { + hybbx_session_write_line(initiator, "Already in a conference."); + return HYBBX_OK; + } + + if (hybbx_conference_invite_pending(initiator)) { + hybbx_session_write_line(initiator, + "Answer the pending conference invite first (y/n)."); + return HYBBX_OK; + } + + storage = hybbx_service_get_storage(service); + if (storage == NULL) { + return HYBBX_ERR_INVALID; + } + + rc = hybbx_storage_resolve_user(storage, partner_spec, &partner_user); + if (rc == HYBBX_ERR_NOT_FOUND) { + hybbx_session_write_line(initiator, "Unknown user."); + return HYBBX_OK; + } + if (rc != HYBBX_OK) { + return rc; + } + + if (!partner_user.active) { + hybbx_session_write_line(initiator, "User is not active."); + return HYBBX_OK; + } + + if (hybbx_user_level_is_guest(partner_user.level)) { + hybbx_session_write_line(initiator, "Cannot conference with a guest."); + return HYBBX_OK; + } + + if (str_ieq(partner_user.username, hybbx_session_username(initiator))) { + hybbx_session_write_line(initiator, "Cannot conference with yourself."); + return HYBBX_OK; + } + + if (!hybbx_session_conference_may_invite(initiator, partner_user.username)) { + hybbx_session_write_line(initiator, + "Invite limit: 2 per user per 30 minutes."); + return HYBBX_OK; + } + + partner_session = conference_find_online_user(service, partner_user.username); + if (partner_session == NULL) { + hybbx_session_write_line(initiator, "User is not online."); + return HYBBX_OK; + } + + if (conference_session_active(partner_session)) { + hybbx_session_write_line(initiator, "User is already in a conference."); + return HYBBX_OK; + } + + if (hybbx_conference_invite_pending(partner_session)) { + hybbx_session_write_line(initiator, + "User has a pending conference invite."); + return HYBBX_OK; + } + + hybbx_session_set_conference_invite(partner_session, + hybbx_session_username(initiator), + topic); + hybbx_session_conference_invite_sent(initiator, partner_user.username); + + snprintf(line, sizeof(line), "Conference invite: %s", topic); + (void)hybbx_msg_send_private(partner_session, + hybbx_session_display_name(initiator), line); + hybbx_session_write_line(partner_session, "Accept? y/n or yes/no"); + + snprintf(line, sizeof(line), "Conference invite sent to %s.", + hybbx_user_display_name(&partner_user)); + hybbx_session_write_line(initiator, line); + + return HYBBX_OK; +} + +hybbx_result_t hybbx_conference_reply_invite(hybbx_service_t *service, + hybbx_session_t *session, + const char *line) +{ + char from_username[HYBBX_USER_NAME_MAX]; + char topic[HYBBX_CONFERENCE_TOPIC_MAX]; + hybbx_session_t *initiator; + char msg[HYBBX_LINE_MAX]; + + if (service == NULL || session == NULL || line == NULL) { + return HYBBX_ERR_INVALID; + } + + if (!hybbx_conference_invite_pending(session)) { + return HYBBX_ERR_INVALID; + } + + if (!invite_reply_yes(line) && !invite_reply_no(line)) { + return HYBBX_ERR_NOT_FOUND; + } + + { + const char *from_ptr = hybbx_session_conference_invite_from(session); + const char *topic_ptr = hybbx_session_conference_invite_topic(session); + + if (from_ptr == NULL || from_ptr[0] == '\0' || + topic_ptr == NULL || topic_ptr[0] == '\0') { + hybbx_session_clear_conference_invite(session); + return HYBBX_ERR_INVALID; + } + + hybbx_strlcpy(from_username, from_ptr, sizeof(from_username)); + hybbx_strlcpy(topic, topic_ptr, sizeof(topic)); + } + + hybbx_session_clear_conference_invite(session); + + if (invite_reply_no(line)) { + initiator = conference_find_online_user(service, from_username); + if (initiator != NULL) { + snprintf(msg, sizeof(msg), + "meet status=declined user=%s", + hybbx_session_display_name(session)); + (void)hybbx_msg_send_system(initiator, msg); + } + hybbx_session_write_line(session, "*** Conference invite declined."); + return HYBBX_OK; + } + + if (conference_session_active(session)) { + hybbx_session_write_line(session, "Already in a conference."); + return HYBBX_OK; + } + + initiator = conference_find_online_user(service, from_username); + if (initiator == NULL) { + hybbx_session_write_line(session, "Inviter is no longer online."); + return HYBBX_OK; + } + + if (conference_session_active(initiator)) { + hybbx_session_write_line(session, "Inviter is already in a conference."); + return HYBBX_OK; + } + + if (conference_activate(service, initiator, session, topic) != HYBBX_OK) { + hybbx_session_write_line(session, "Conference could not be started."); + return HYBBX_OK; + } + + snprintf(msg, sizeof(msg), "meet status=accepted user=%s", + hybbx_session_display_name(session)); + hybbx_session_write_line(initiator, msg); + if (hybbx_monitor_is_active(initiator)) { + hybbx_monitor_broadcast(service, msg); + } + + return HYBBX_OK; +} + +void hybbx_conference_invite_tick(hybbx_service_t *service, + hybbx_session_t *session) +{ + time_t deadline; + time_t now; + char from_username[HYBBX_USER_NAME_MAX]; + const char *from_ptr; + hybbx_session_t *initiator; + char msg[HYBBX_LINE_MAX]; + + if (service == NULL || session == NULL) { + return; + } + + if (!hybbx_conference_invite_pending(session)) { + return; + } + + deadline = hybbx_session_conference_invite_deadline(session); + if (deadline == 0) { + return; + } + + now = time(NULL); + if (now < deadline) { + return; + } + + from_ptr = hybbx_session_conference_invite_from(session); + if (from_ptr != NULL) { + hybbx_strlcpy(from_username, from_ptr, sizeof(from_username)); + } else { + from_username[0] = '\0'; + } + + hybbx_session_clear_conference_invite(session); + hybbx_session_write_line(session, "*** Conference invite timed out."); + + if (from_username[0] != '\0') { + initiator = conference_find_online_user(service, from_username); + if (initiator != NULL) { + snprintf(msg, sizeof(msg), "meet status=timeout user=%s", + hybbx_session_display_name(session)); + hybbx_session_write_line(initiator, msg); + if (hybbx_monitor_is_active(initiator)) { + hybbx_monitor_broadcast(service, msg); + } + } + } +} + +hybbx_result_t hybbx_conference_monitor_meet(hybbx_service_t *service, + hybbx_session_t *initiator, + const char *partner_spec, + int force) +{ + const hybbx_monitor_config_t *mcfg; + hybbx_storage_t *storage; + hybbx_user_record_t partner_user; + hybbx_session_t *partner_session; + char line[HYBBX_LINE_MAX]; + hybbx_result_t rc; + const char *topic = "monitor"; + unsigned timeout_sec = 20u; + + if (service == NULL || initiator == NULL || partner_spec == NULL || + partner_spec[0] == '\0') { + return HYBBX_ERR_INVALID; + } + + mcfg = hybbx_monitor_config_get(); + if (mcfg != NULL) { + timeout_sec = mcfg->invite_timeout_sec; + } + + if (hybbx_session_is_guest(initiator)) { + hybbx_session_write_line(initiator, "Guests cannot use meet."); + return HYBBX_ERR_DENIED; + } + + if (conference_session_active(initiator)) { + hybbx_session_write_line(initiator, "Already in a conference."); + return HYBBX_OK; + } + + if (hybbx_conference_invite_pending(initiator)) { + hybbx_session_write_line(initiator, + "Answer the pending conference invite first (y/n)."); + return HYBBX_OK; + } + + storage = hybbx_service_get_storage(service); + if (storage == NULL) { + return HYBBX_ERR_INVALID; + } + + rc = hybbx_storage_resolve_user(storage, partner_spec, &partner_user); + if (rc == HYBBX_ERR_NOT_FOUND) { + hybbx_session_write_line(initiator, "Unknown user."); + return HYBBX_OK; + } + if (rc != HYBBX_OK) { + return rc; + } + + if (!partner_user.active) { + hybbx_session_write_line(initiator, "User is not active."); + return HYBBX_OK; + } + + if (hybbx_user_level_is_guest(partner_user.level)) { + hybbx_session_write_line(initiator, "Cannot conference with a guest."); + return HYBBX_OK; + } + + if (str_ieq(partner_user.username, hybbx_session_username(initiator))) { + hybbx_session_write_line(initiator, "Cannot conference with yourself."); + return HYBBX_OK; + } + + partner_session = conference_find_online_user(service, partner_user.username); + if (partner_session == NULL) { + hybbx_session_write_line(initiator, "User is not online."); + return HYBBX_OK; + } + + if (conference_session_active(partner_session)) { + hybbx_session_write_line(initiator, "User is already in a conference."); + return HYBBX_OK; + } + + if (force) { + if (conference_activate(service, initiator, partner_session, + topic) != HYBBX_OK) { + hybbx_session_write_line(initiator, + "Conference could not be started."); + return HYBBX_OK; + } + snprintf(line, sizeof(line), "meet status=forced user=%s", + hybbx_user_display_name(&partner_user)); + hybbx_session_write_line(initiator, line); + hybbx_monitor_broadcast(service, line); + hybbx_session_write_line(partner_session, + "You were placed into a conference by Sysop."); + return HYBBX_OK; + } + + if (hybbx_conference_invite_pending(partner_session)) { + hybbx_session_write_line(initiator, + "User has a pending conference invite."); + return HYBBX_OK; + } + + hybbx_session_set_conference_invite(partner_session, + hybbx_session_username(initiator), + topic); + hybbx_session_set_conference_invite_deadline( + partner_session, time(NULL) + (time_t)timeout_sec); + + snprintf(line, sizeof(line), + "*** Conference invite from %s: %s (reply in %u s)", + hybbx_session_display_name(initiator), topic, timeout_sec); + hybbx_session_write_line(partner_session, line); + hybbx_session_write_line(partner_session, "*** Accept? y/n or yes/no"); + + snprintf(line, sizeof(line), + "meet status=pending user=%s timeout=%us", + hybbx_user_display_name(&partner_user), timeout_sec); + hybbx_session_write_line(initiator, line); + hybbx_monitor_broadcast(service, line); + + return HYBBX_OK; +} + +typedef struct conference_post_ctx { + hybbx_session_t *from; + const char *message; + const char *from_user; + const char *from_login; +} conference_post_ctx_t; + +static void conference_post_visitor(hybbx_session_t *session, void *userdata) +{ + conference_post_ctx_t *ctx = (conference_post_ctx_t *)userdata; + char line[HYBBX_USER_NAME_MAX + HYBBX_LINE_MAX + 16]; + const char *session_login; + + if (session == NULL || ctx == NULL || ctx->message == NULL || + ctx->from == NULL || ctx->from_login == NULL) { + return; + } + + if (!conference_session_active(session)) { + return; + } + + if (session == ctx->from) { + snprintf(line, sizeof(line), "ME: %s", ctx->message); + hybbx_session_write_line(session, line); + return; + } + + session_login = hybbx_session_username(session); + if (session_login == NULL || + !str_ieq(hybbx_session_conference_partner(session), ctx->from_login) || + !str_ieq(hybbx_session_conference_partner(ctx->from), session_login)) { + return; + } + + snprintf(line, sizeof(line), "%s: %s", ctx->from_user, ctx->message); + hybbx_session_write_line(session, line); +} + +hybbx_result_t hybbx_conference_post(hybbx_service_t *service, + hybbx_session_t *from, + const char *message) +{ + conference_post_ctx_t ctx; + const hybbx_chat_config_t *chat; + + if (service == NULL || from == NULL || message == NULL) { + return HYBBX_ERR_INVALID; + } + + if (message[0] == '\0') { + return HYBBX_OK; + } + + if (!conference_session_active(from)) { + return HYBBX_ERR_INVALID; + } + + chat = hybbx_service_get_chat(service); + if (chat == NULL) { + return HYBBX_ERR_INVALID; + } + + if (strlen(message) > chat->message_max) { + return HYBBX_ERR_INVALID; + } + + ctx.from = from; + ctx.message = message; + ctx.from_user = hybbx_session_display_name(from); + ctx.from_login = hybbx_session_username(from); + if (ctx.from_login == NULL || ctx.from_login[0] == '\0') { + return HYBBX_ERR_INVALID; + } + + hybbx_service_visit_sessions(service, conference_post_visitor, &ctx); + return HYBBX_OK; +} + +void hybbx_conference_area_leaving(hybbx_session_t *session) +{ + const char *partner_name; + + if (session == NULL) { + return; + } + + partner_name = hybbx_session_conference_partner(session); + hybbx_session_clear_conference(session); + + if (partner_name != NULL && partner_name[0] != '\0') { + conference_notify_partner_left(session, partner_name); + } +} + +void hybbx_conference_session_closed(hybbx_session_t *session) +{ + hybbx_service_t *service; + const char *username; + + if (session == NULL) { + return; + } + + if (hybbx_conference_invite_pending(session)) { + hybbx_session_clear_conference_invite(session); + } + + if (!conference_session_active(session)) { + service = hybbx_session_service(session); + username = hybbx_session_username(session); + if (service != NULL && username != NULL && username[0] != '\0') { + conference_cancel_invites_from(service, username); + } + return; + } + + hybbx_conference_area_leaving(session); + + service = hybbx_session_service(session); + username = hybbx_session_username(session); + if (service != NULL && username != NULL && username[0] != '\0') { + conference_cancel_invites_from(service, username); + } +} -- cgit v1.3.1