diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..40db1b7 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +CC = gcc +CFLAGS = -Wall -Wextra $(shell pkg-config --cflags libstrophe) +LDFLAGS = $(shell pkg-config --libs libstrophe) + +xmpp_bot: xmpp_bot.c + $(CC) $(CFLAGS) -o xmpp_bot xmpp_bot.c $(LDFLAGS) + +clean: + rm -f xmpp_bot diff --git a/xmpp_bot.c b/xmpp_bot.c new file mode 100644 index 0000000..05c8e2a --- /dev/null +++ b/xmpp_bot.c @@ -0,0 +1,186 @@ +#include +#include +#include +#include + +#define SERVER "whatevermaybe.net" +#define ROOM_JID "archlinux@chat.hax.al" +#define BOT_NICKNAME "erdhe" +#define BOT_JID "bot@hax.al" +#define BOT_PASSWORD "passwordfield" + +/* Add UNUSED macro to suppress warnings */ +#define UNUSED(x) (void)(x) + +/* List to track users who have already received the whisper */ +typedef struct UserList { + char **users; + int count; + int size; +} UserList; + +/* Global user list */ +UserList userlist; + +/* Flag to determine whether the bot has finished joining the room */ +int bot_joined = 0; + +/* Add user to list */ +void userlist_add(UserList *list, const char *jid) { + if (list->count >= list->size) { + list->size *= 2; + list->users = realloc(list->users, list->size * sizeof(char *)); + } + list->users[list->count] = strdup(jid); + list->count++; +} + +/* Check if user is in the list */ +int userlist_contains(UserList *list, const char *jid) { + for (int i = 0; i < list->count; i++) { + if (strcmp(list->users[i], jid) == 0) { + return 1; + } + } + return 0; +} + +/* Send a whisper message */ +void send_whisper(xmpp_conn_t * const conn, xmpp_ctx_t *ctx, const char *to_jid, const char *message) { + xmpp_stanza_t *msg = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(msg, "message"); + xmpp_stanza_set_type(msg, "chat"); + xmpp_stanza_set_attribute(msg, "to", to_jid); + + xmpp_stanza_t *body = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(body, "body"); + xmpp_stanza_t *text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(text, message); + xmpp_stanza_add_child(body, text); + xmpp_stanza_add_child(msg, body); + xmpp_stanza_release(text); + xmpp_stanza_release(body); + + xmpp_send(conn, msg); + xmpp_stanza_release(msg); + + fprintf(stderr, "Sent whisper to %s: %s\n", to_jid, message); +} + +/* Handle presence stanzas */ +int presence_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata) { + const char *from = xmpp_stanza_get_attribute(stanza, "from"); // Fix const issue + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + + /* Ensure the presence is related to the room */ + if (from && strstr(from, ROOM_JID)) { + const char *resource = strchr(from, '/'); + if (resource && strcmp(resource + 1, BOT_NICKNAME) != 0) { // Ignore presence from the bot itself + /* Send whisper only if the bot has fully joined and user hasn't received it before */ + if (bot_joined && !userlist_contains(&userlist, from)) { + send_whisper(conn, ctx, from, "Welcome to the room!"); + userlist_add(&userlist, from); + } else { + /* Add user to the list if the bot is still joining */ + userlist_add(&userlist, from); + } + } + } + + return 1; +} + +/* Handle connection events */ +void conn_handler(xmpp_conn_t * const conn, const xmpp_conn_event_t status, + const int error, xmpp_stream_error_t * const stream_error, + void * const userdata) { + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + + if (status == XMPP_CONN_CONNECT) { + fprintf(stderr, "Connected to server %s\n", SERVER); + + /* Add handler for presence stanzas */ + xmpp_handler_add(conn, presence_handler, NULL, "presence", NULL, ctx); + + /* Create presence stanza to join room */ + xmpp_stanza_t *presence = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(presence, "presence"); + + /* Set the "to" attribute as room_jid/nickname */ + char to_buffer[512]; + snprintf(to_buffer, sizeof(to_buffer), "%s/%s", ROOM_JID, BOT_NICKNAME); + xmpp_stanza_set_attribute(presence, "to", to_buffer); + + /* Add MUC namespace */ + xmpp_stanza_t *x = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(x, "x"); + xmpp_stanza_set_ns(x, "http://jabber.org/protocol/muc"); + xmpp_stanza_add_child(presence, x); + xmpp_stanza_release(x); + + /* Send presence stanza */ + xmpp_send(conn, presence); + xmpp_stanza_release(presence); + + fprintf(stderr, "Sent presence to join room: %s\n", to_buffer); + + /* Mark the bot as fully joined after the initial presence handling */ + bot_joined = 1; + + } else { + if (error) { + fprintf(stderr, "Connection error: %d\n", error); + if (stream_error) { + fprintf(stderr, "Stream error type: %d\n", stream_error->type); + } + } + fprintf(stderr, "Disconnected from server\n"); + xmpp_stop(ctx); + } +} + +/* Main function */ +int main(void) { + xmpp_ctx_t *ctx; + xmpp_conn_t *conn; + xmpp_log_t *log; + + /* Initialize userlist */ + userlist.size = 10; // Initial size + userlist.count = 0; + userlist.users = malloc(userlist.size * sizeof(char *)); + + xmpp_initialize(); + + log = xmpp_get_default_logger(XMPP_LEVEL_DEBUG); /* Use default logger instead of creating new one */ + ctx = xmpp_ctx_new(NULL, log); + + conn = xmpp_conn_new(ctx); + + xmpp_conn_set_jid(conn, BOT_JID); + xmpp_conn_set_pass(conn, BOT_PASSWORD); + + /* Explicitly set the server */ + xmpp_conn_set_flags(conn, XMPP_CONN_FLAG_MANDATORY_TLS); + + fprintf(stderr, "Connecting to %s...\n", SERVER); + + if (xmpp_connect_client(conn, SERVER, 0, conn_handler, ctx) == XMPP_EOK) { + fprintf(stderr, "Running connection...\n"); + xmpp_run(ctx); + } else { + fprintf(stderr, "Failed to start connection\n"); + } + + xmpp_conn_release(conn); + xmpp_ctx_free(ctx); + xmpp_shutdown(); + + /* Free the userlist */ + for (int i = 0; i < userlist.count; i++) { + free(userlist.users[i]); + } + free(userlist.users); + + return 0; +}