From 441dc892caba755faf3f53a46a23ac9ff46ce28d Mon Sep 17 00:00:00 2001 From: jfreegman Date: Sun, 22 Dec 2024 15:44:59 -0500 Subject: [PATCH] feat: Allow friend invites from group and conference windows Friends from the friend list can now be invited to groups and conferences with the /invite and /cinvite commands respectively. --- src/chat_commands.c | 4 +-- src/chat_commands.h | 4 +-- src/conference.c | 19 +++++++++++- src/conference_commands.c | 59 +++++++++++++++++++++++++++++++++++ src/conference_commands.h | 1 + src/execute.c | 10 ++++-- src/friendlist.c | 42 +++++++++++++++++++++++++ src/friendlist.h | 23 ++++++++++++++ src/groupchat_commands.c | 65 ++++++++++++++++++++++++++++++++++++--- src/groupchat_commands.h | 1 + src/groupchats.c | 24 ++++++++++++--- src/help.c | 10 +++--- 12 files changed, 242 insertions(+), 20 deletions(-) diff --git a/src/chat_commands.c b/src/chat_commands.c index f92887a0e..ab01606b1 100644 --- a/src/chat_commands.c +++ b/src/chat_commands.c @@ -119,7 +119,7 @@ void cmd_cancelfile(WINDOW *window, ToxWindow *self, Toxic *toxic, int argc, cha close_file_transfer(self, toxic, ft, TOX_FILE_CONTROL_CANCEL, msg, silent); } -void cmd_conference_invite(WINDOW *window, ToxWindow *self, Toxic *toxic, int argc, char (*argv)[MAX_STR_SIZE]) +void cmd_invite_to_conference(WINDOW *window, ToxWindow *self, Toxic *toxic, int argc, char (*argv)[MAX_STR_SIZE]) { UNUSED_VAR(window); @@ -275,7 +275,7 @@ void cmd_group_accept(WINDOW *window, ToxWindow *self, Toxic *toxic, int argc, c } } -void cmd_group_invite(WINDOW *window, ToxWindow *self, Toxic *toxic, int argc, char (*argv)[MAX_STR_SIZE]) +void cmd_invite_to_group(WINDOW *window, ToxWindow *self, Toxic *toxic, int argc, char (*argv)[MAX_STR_SIZE]) { if (toxic == NULL || self == NULL) { return; diff --git a/src/chat_commands.h b/src/chat_commands.h index 58d799360..d034d85be 100644 --- a/src/chat_commands.h +++ b/src/chat_commands.h @@ -14,10 +14,10 @@ void cmd_autoaccept_files(WINDOW *window, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_cancelfile(WINDOW *window, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); -void cmd_conference_invite(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_invite_to_conference(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_conference_join(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_group_accept(WINDOW *window, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); -void cmd_group_invite(WINDOW *window, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_invite_to_group(WINDOW *window, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_game_join(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_savefile(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_sendfile(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); diff --git a/src/conference.c b/src/conference.c index d348e15dc..99dacb614 100644 --- a/src/conference.c +++ b/src/conference.c @@ -37,6 +37,7 @@ #include "autocomplete.h" #include "conference.h" #include "execute.h" +#include "friendlist.h" #include "help.h" #include "input.h" #include "line_info.h" @@ -67,14 +68,15 @@ static const char *const conference_cmd_list[] = { #endif "/avatar", "/chatid", + "/cinvite", "/clear", "/close", "/color", + "/conference", "/connect", "/decline", "/exit", "/group", - "/conference", #ifdef GAMES "/game", #endif @@ -662,6 +664,7 @@ static void set_peer_audio_position(Tox *tox, uint32_t conferencenum, uint32_t p const float angle = asinf(peer_posn - (float)(num_posns - 1) / 2); set_source_position(peer->audio_out_idx, sinf(angle), cosf(angle), 0); } + #endif // AUDIO @@ -965,6 +968,19 @@ static bool conference_onKey(ToxWindow *self, Toxic *toxic, wint_t key, bool ltr } } else if (wcsncmp(ctx->line, L"/avatar ", wcslen(L"/avatar ")) == 0) { diff = dir_match(self, toxic, ctx->line, L"/avatar"); + } else if (wcsncmp(ctx->line, L"/cinvite ", wcslen(L"/cinvite ")) == 0) { + size_t num_friends = friendlist_get_count(); + char **friend_names = (char **) malloc_ptr_array(num_friends, TOX_MAX_NAME_LENGTH); + + if (friend_names != NULL) { + friendlist_get_names(friend_names, num_friends, TOX_MAX_NAME_LENGTH); + diff = complete_line(self, toxic, (const char *const *) friend_names, num_friends); + free_ptr_array((void **) friend_names); + } else { + diff = -1; + num_friends = 0; + fprintf(stderr, "Failed to allocate memory for friends name list\n"); + } } #ifdef PYTHON @@ -1602,4 +1618,5 @@ float conference_get_VAD_threshold(uint32_t conferencenum) return device_get_VAD_threshold(chat->audio_in_idx); } + #endif // AUDIO diff --git a/src/conference_commands.c b/src/conference_commands.c index 650244349..a7d5a3101 100644 --- a/src/conference_commands.c +++ b/src/conference_commands.c @@ -12,6 +12,7 @@ #include #include "conference.h" +#include "friendlist.h" #include "line_info.h" #include "log.h" #include "misc_tools.h" @@ -51,6 +52,63 @@ void cmd_conference_chatid(WINDOW *window, ToxWindow *self, Toxic *toxic, int ar line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "%s", id_string); } +void cmd_conference_invite(WINDOW *window, ToxWindow *self, Toxic *toxic, int argc, char (*argv)[MAX_STR_SIZE]) +{ + UNUSED_VAR(window); + + if (toxic == NULL || self == NULL) { + return; + } + + Tox *tox = toxic->tox; + const Client_Config *c_config = toxic->c_config; + + if (argc != 1) { + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "Friend name required."); + return; + } + + const char *nick = argv[1]; + const int64_t friend_number = get_friend_number_name(nick, strlen(nick)); + + if (friend_number == -1) { + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, + "Friend '%s' not found (names are case-sensitive)", nick); + return; + } + + if (friend_number == -2) { + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, + "There are multiple friends in your friend list with this name. To invite this friend, navigate to their chat window and try again"); + return; + } + + const long int conferencenum = self->num; + + Tox_Err_Conference_Invite err; + + if (!tox_conference_invite(tox, (uint32_t)friend_number, conferencenum, &err)) { + switch (err) { + case TOX_ERR_GROUP_INVITE_FRIEND_FRIEND_NOT_FOUND: { + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "%s is not in your friends list.", nick); + return; + } + + case TOX_ERR_GROUP_INVITE_FRIEND_INVITE_FAIL: { + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "Friend is offline."); + return; + } + + default: { + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "Invite failed (error %d)", err); + return; + } + } + } + + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "Invited %s to the conference.", nick); +} + void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Toxic *toxic, int argc, char (*argv)[MAX_STR_SIZE]) { UNUSED_VAR(window); @@ -248,4 +306,5 @@ void cmd_conference_push_to_talk(WINDOW *window, ToxWindow *self, Toxic *toxic, print_err(self, c_config, enable ? "Push-To-Talk is enabled. Push F2 to activate" : "Push-To-Talk is disabled"); } + #endif /* AUDIO */ diff --git a/src/conference_commands.h b/src/conference_commands.h index c6ff09841..ce54671a3 100644 --- a/src/conference_commands.h +++ b/src/conference_commands.h @@ -13,6 +13,7 @@ #include "windows.h" void cmd_conference_chatid(WINDOW *window, ToxWindow *self, Toxic *toxic, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_conference_invite(WINDOW *window, ToxWindow *self, Toxic *toxic, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_conference_set_title(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_enable_audio(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_conference_mute(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); diff --git a/src/execute.c b/src/execute.c index a0202f364..e83bd98f3 100644 --- a/src/execute.c +++ b/src/execute.c @@ -71,10 +71,10 @@ static struct cmd_func global_commands[] = { static struct cmd_func chat_commands[] = { { "/autoaccept", cmd_autoaccept_files }, { "/cancel", cmd_cancelfile }, - { "/cinvite", cmd_conference_invite }, + { "/cinvite", cmd_invite_to_conference }, { "/cjoin", cmd_conference_join }, { "/gaccept", cmd_group_accept }, - { "/invite", cmd_group_invite }, + { "/invite", cmd_invite_to_group }, #ifdef GAMES { "/play", cmd_game_join }, #endif @@ -99,6 +99,7 @@ static struct cmd_func chat_commands[] = { static struct cmd_func conference_commands[] = { { "/chatid", cmd_conference_chatid }, + { "/cinvite", cmd_conference_invite }, { "/title", cmd_conference_set_title }, #ifdef AUDIO @@ -112,8 +113,9 @@ static struct cmd_func conference_commands[] = { static struct cmd_func groupchat_commands[] = { { "/chatid", cmd_chatid }, - { "/disconnect", cmd_disconnect }, + { "/disconnect", cmd_disconnect }, { "/ignore", cmd_ignore }, + { "/invite", cmd_group_invite }, { "/kick", cmd_kick }, { "/list", cmd_list }, { "/locktopic", cmd_set_topic_lock }, @@ -137,9 +139,11 @@ static struct cmd_func groupchat_commands[] = { static const char special_commands[][MAX_CMDNAME_SIZE] = { "/add", "/avatar", + "/cinvite", "/gaccept", "/group", "/ignore", + "/invite", "/kick", "/mod", "/nick", diff --git a/src/friendlist.c b/src/friendlist.c index f5eed70bc..26e1953a4 100644 --- a/src/friendlist.c +++ b/src/friendlist.c @@ -1424,6 +1424,24 @@ void disable_friend_window(uint32_t f_num) Friends.list[f_num].window_id = -1; } +size_t friendlist_get_count(void) +{ + return Friends.num_friends; +} + +void friendlist_get_names(char **names, size_t max_names, size_t max_name_size) +{ + if (Friends.num_friends == 0) { + return; + } + + const size_t bytes_to_copy = MIN(sizeof(Friends.list[0].name), max_name_size); + + for (size_t i = 0; i < max_names && i < Friends.num_friends; ++i) { + snprintf(names[i], bytes_to_copy, "%s", Friends.list[i].name); + } +} + #ifdef AUDIO static void friendlist_onAV(ToxWindow *self, Toxic *toxic, uint32_t friend_number, int state) { @@ -1455,6 +1473,7 @@ static void friendlist_onAV(ToxWindow *self, Toxic *toxic, uint32_t friend_numbe set_active_window_by_id(toxic->windows, window_id); } } + #endif /* AUDIO */ /* Returns a friend's status */ @@ -1477,6 +1496,29 @@ Tox_Connection get_friend_connection_status(uint32_t friendnumber) return Friends.list[friendnumber].connection_status; } +int64_t get_friend_number_name(const char *name, uint16_t length) +{ + int64_t num = -1; + bool match_found = false; + + for (size_t i = 0; i < Friends.max_idx; ++i) { + if (length != Friends.list[i].namelength) { + continue; + } + + if (memcmp(name, Friends.list[i].name, length) == 0) { + if (match_found) { + return -2; + } + + num = Friends.list[i].num; + match_found = true; + } + } + + return num; +} + /* * Returns true if friend associated with `public_key` is in the block list. * diff --git a/src/friendlist.h b/src/friendlist.h index 93eed3ae0..c01da6deb 100644 --- a/src/friendlist.h +++ b/src/friendlist.h @@ -124,6 +124,19 @@ void friendlist_onFriendAdded(ToxWindow *self, Toxic *toxic, uint32_t num, bool Tox_User_Status get_friend_status(uint32_t friendnumber); Tox_Connection get_friend_connection_status(uint32_t friendnumber); +/* + * Returns the number of friends in the friend list. + */ +size_t friendlist_get_count(void); + +/* + * Copies names from the friends list into the `names` array. + * + * @max_names The maximum number of names to copy. + * @max_name_size The maximum number of bytes to copy per name. + */ +void friendlist_get_names(char **names, size_t max_names, size_t max_name_size); + /* * Loads the list of blocked peers from `path`. * @@ -162,6 +175,16 @@ bool friend_get_auto_accept_files(uint32_t friendnumber); */ uint16_t get_friend_name(char *buf, size_t buf_size, uint32_t friendnumber); +/* + * Returns the friend number associated with `name`. + * + * @length The length of the name. + * + * Returns -1 if `name` does not designate a friend in the friend list. + * Returns -2 if `name` matches more than one friend in the friend list. + */ +int64_t get_friend_number_name(const char *name, uint16_t length); + /* * Puts a friend's public key in `pk`, which must have room for at least * TOX_PUBLIC_KEY_SIZE bytes. diff --git a/src/groupchat_commands.c b/src/groupchat_commands.c index ed15ee184..1426e01fd 100644 --- a/src/groupchat_commands.c +++ b/src/groupchat_commands.c @@ -11,12 +11,13 @@ #include #include -#include "toxic.h" -#include "windows.h" +#include "friendlist.h" +#include "groupchats.h" #include "line_info.h" -#include "misc_tools.h" #include "log.h" -#include "groupchats.h" +#include "misc_tools.h" +#include "toxic.h" +#include "windows.h" extern GroupChat groupchats[MAX_GROUPCHAT_NUM]; @@ -168,6 +169,62 @@ void cmd_ignore(WINDOW *window, ToxWindow *self, Toxic *toxic, int argc, char (* group_toggle_peer_ignore(self->num, peer_id, true); } +void cmd_group_invite(WINDOW *window, ToxWindow *self, Toxic *toxic, int argc, char (*argv)[MAX_STR_SIZE]) +{ + if (toxic == NULL || self == NULL) { + return; + } + + Tox *tox = toxic->tox; + const Client_Config *c_config = toxic->c_config; + + if (argc != 1) { + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "Friend name required."); + return; + } + + const char *nick = argv[1]; + const int64_t friend_number = get_friend_number_name(nick, strlen(nick)); + + if (friend_number == -1) { + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, + "Friend '%s' not found (names are case-sensitive)", nick); + return; + } + + if (friend_number == -2) { + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, + "There are multiple friends in your friend list with this name. To invite this friend, navigate to their chat window and try again"); + return; + } + + const int groupnumber = self->num; + + Tox_Err_Group_Invite_Friend err; + + if (!tox_group_invite_friend(tox, groupnumber, (uint32_t)friend_number, &err)) { + switch (err) { + case TOX_ERR_GROUP_INVITE_FRIEND_FRIEND_NOT_FOUND: { + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "Friend not found."); + return; + } + + case TOX_ERR_GROUP_INVITE_FRIEND_INVITE_FAIL: { + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "Friend is offline."); + return; + } + + default: { + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "Invite failed (error %d)", err); + return; + } + + } + } + + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "Invited %s to the group.", nick); +} + void cmd_kick(WINDOW *window, ToxWindow *self, Toxic *toxic, int argc, char (*argv)[MAX_STR_SIZE]) { UNUSED_VAR(window); diff --git a/src/groupchat_commands.h b/src/groupchat_commands.h index 1aa6c7142..0ed482a1b 100644 --- a/src/groupchat_commands.h +++ b/src/groupchat_commands.h @@ -14,6 +14,7 @@ void cmd_chatid(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_disconnect(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); +void cmd_group_invite(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_group_nick(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_ignore(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); void cmd_kick(WINDOW *, ToxWindow *, Toxic *, int argc, char (*argv)[MAX_STR_SIZE]); diff --git a/src/groupchats.c b/src/groupchats.c index dd673a717..e87b304a9 100644 --- a/src/groupchats.c +++ b/src/groupchats.c @@ -37,6 +37,7 @@ #include "execute.h" #include "misc_tools.h" #include "groupchats.h" +#include "friendlist.h" #include "toxic_strings.h" #include "log.h" #include "line_info.h" @@ -73,6 +74,7 @@ static const char *const group_cmd_list[] = { "/group", "/help", "/ignore", + "/invite", "/join", "/kick", "/list", @@ -1932,14 +1934,28 @@ static bool groupchat_onKey(ToxWindow *self, Toxic *toxic, wint_t key, bool ltr) if (key == L'\t') { /* TAB key: auto-completes peer name or command */ input_ret = true; + /* TODO: make this not suck */ if (ctx->len > 0) { int diff; - /* TODO: make this not suck */ - if (ctx->line[0] != L'/' || wcschr(ctx->line, L' ') != NULL) { - diff = complete_line(self, toxic, (const char *const *) chat->name_list, chat->num_peers); - } else if (wcsncmp(ctx->line, L"/avatar \"", wcslen(L"/avatar \"")) == 0) { + if (wcsncmp(ctx->line, L"/invite ", wcslen(L"/invite ")) == 0) { + size_t num_friends = friendlist_get_count(); + char **friend_names = (char **) malloc_ptr_array(num_friends, TOX_MAX_NAME_LENGTH); + + if (friend_names != NULL) { + friendlist_get_names(friend_names, num_friends, TOX_MAX_NAME_LENGTH); + diff = complete_line(self, toxic, (const char *const *) friend_names, num_friends); + free_ptr_array((void **) friend_names); + } else { + diff = -1; + num_friends = 0; + fprintf(stderr, "Failed to allocate memory for friends name list\n"); + } + + } else if (wcsncmp(ctx->line, L"/avatar ", wcslen(L"/avatar ")) == 0) { diff = dir_match(self, toxic, ctx->line, L"/avatar"); + } else if (ctx->line[0] != L'/' || wcschr(ctx->line, L' ') != NULL) { + diff = complete_line(self, toxic, (const char *const *) chat->name_list, chat->num_peers); } else { diff = complete_line(self, toxic, group_cmd_list, sizeof(group_cmd_list) / sizeof(char *)); } diff --git a/src/help.c b/src/help.c index 7487be25a..2e1739697 100644 --- a/src/help.c +++ b/src/help.c @@ -234,9 +234,9 @@ static void help_draw_chat(ToxWindow *self) wattroff(win, A_BOLD | COLOR_PAIR(RED)); wprintw(win, " /autoaccept | : Toggle auto-accepting file transfers\n"); - wprintw(win, " /cinvite : Invite contact to a conference \n"); + wprintw(win, " /cinvite : Invite contact to a conference \n"); wprintw(win, " /cjoin : Join a pending conference\n"); - wprintw(win, " /invite : Invite contact to a groupchat \n"); + wprintw(win, " /invite : Invite contact to a groupchat \n"); wprintw(win, " /gaccept : Accept a pending groupchat invite\n"); wprintw(win, " /sendfile : Send a file\n"); wprintw(win, " /savefile : Receive a file\n"); @@ -291,6 +291,7 @@ static void help_draw_groupchats(ToxWindow *self) wprintw(win, " /disconnect : Disconnect from the group (credentials retained)\n"); wprintw(win, " /ignore | : Ignore a peer\n"); wprintw(win, " /unignore | : Unignore a peer\n"); + wprintw(win, " /invite : Invite a friend to the group\n"); wprintw(win, " /kick | : Remove a peer from the group\n"); wprintw(win, " /list : Print a list of peers currently in the group\n"); wprintw(win, " /locktopic : Set the topic lock: on | off\n"); @@ -353,6 +354,7 @@ static void help_draw_conference(ToxWindow *self) wattroff(win, A_BOLD | COLOR_PAIR(RED)); wprintw(win, " /chatid : Print this conference's ID\n"); + wprintw(win, " /cinvite : Invite a friend to this conference\n"); wprintw(win, " /title : Show/set conference title\n"); #ifdef AUDIO wattron(win, A_BOLD); @@ -456,7 +458,7 @@ void help_onKey(ToxWindow *self, wint_t key) break; case L'o': - height = 7; + height = 8; #ifdef AUDIO height += 7; #endif @@ -488,7 +490,7 @@ void help_onKey(ToxWindow *self, wint_t key) break; case L'r': - help_init_window(self, 27, 80); + help_init_window(self, 28, 80); self->help->type = HELP_GROUP; break; }