about summary refs log tree commit diff stats
path: root/src/command
diff options
context:
space:
mode:
Diffstat (limited to 'src/command')
-rw-r--r--src/command/cmd_ac.c250
-rw-r--r--src/command/cmd_defs.c136
-rw-r--r--src/command/cmd_funcs.c990
-rw-r--r--src/command/cmd_funcs.h9
4 files changed, 1380 insertions, 5 deletions
diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c
index 93a503d1..9a839258 100644
--- a/src/command/cmd_ac.c
+++ b/src/command/cmd_ac.c
@@ -41,6 +41,7 @@
 #include <assert.h>
 #include <libgen.h>
 #include <dirent.h>
+#include <ctype.h>
 
 #include "common.h"
 #include "config/preferences.h"
@@ -131,6 +132,7 @@ static char* _lastactivity_autocomplete(ProfWin* window, const char* const input
 static char* _intype_autocomplete(ProfWin* window, const char* const input, gboolean previous);
 static char* _mood_autocomplete(ProfWin* window, const char* const input, gboolean previous);
 static char* _adhoc_cmd_autocomplete(ProfWin* window, const char* const input, gboolean previous);
+static char* _vcard_autocomplete(ProfWin* window, const char* const input, gboolean previous);
 
 static char* _script_autocomplete_func(const char* const prefix, gboolean previous, void* context);
 
@@ -276,6 +278,15 @@ static Autocomplete mood_ac;
 static Autocomplete mood_type_ac;
 static Autocomplete adhoc_cmd_ac;
 static Autocomplete lastactivity_ac;
+static Autocomplete vcard_ac;
+static Autocomplete vcard_photo_ac;
+static Autocomplete vcard_element_ac;
+static Autocomplete vcard_set_ac;
+static Autocomplete vcard_name_ac;
+static Autocomplete vcard_set_param_ac;
+static Autocomplete vcard_togglable_param_ac;
+static Autocomplete vcard_toggle_ac;
+static Autocomplete vcard_address_type_ac;
 
 /*!
  * \brief Initialization of auto completion for commands.
@@ -1181,6 +1192,89 @@ cmd_ac_init(void)
     lastactivity_ac = autocomplete_new();
     autocomplete_add(lastactivity_ac, "set");
     autocomplete_add(lastactivity_ac, "get");
+
+    vcard_ac = autocomplete_new();
+    autocomplete_add(vcard_ac, "get");
+    autocomplete_add(vcard_ac, "photo");
+    autocomplete_add(vcard_ac, "set");
+    autocomplete_add(vcard_ac, "add");
+    autocomplete_add(vcard_ac, "remove");
+    autocomplete_add(vcard_ac, "save");
+
+    vcard_photo_ac = autocomplete_new();
+    autocomplete_add(vcard_photo_ac, "open");
+    autocomplete_add(vcard_photo_ac, "save");
+
+    vcard_element_ac = autocomplete_new();
+    autocomplete_add(vcard_element_ac, "nickname");
+    autocomplete_add(vcard_element_ac, "birthday");
+    autocomplete_add(vcard_element_ac, "address");
+    autocomplete_add(vcard_element_ac, "tel");
+    autocomplete_add(vcard_element_ac, "email");
+    autocomplete_add(vcard_element_ac, "jid");
+    autocomplete_add(vcard_element_ac, "title");
+    autocomplete_add(vcard_element_ac, "role");
+    autocomplete_add(vcard_element_ac, "note");
+    autocomplete_add(vcard_element_ac, "url");
+
+    vcard_set_ac = autocomplete_new();
+    autocomplete_add(vcard_set_ac, "fullname");
+    autocomplete_add(vcard_set_ac, "name");
+
+    vcard_name_ac = autocomplete_new();
+    autocomplete_add(vcard_name_ac, "family");
+    autocomplete_add(vcard_name_ac, "given");
+    autocomplete_add(vcard_name_ac, "middle");
+    autocomplete_add(vcard_name_ac, "prefix");
+    autocomplete_add(vcard_name_ac, "suffix");
+
+    vcard_set_param_ac = autocomplete_new();
+    autocomplete_add(vcard_set_param_ac, "pobox");
+    autocomplete_add(vcard_set_param_ac, "extaddr");
+    autocomplete_add(vcard_set_param_ac, "street");
+    autocomplete_add(vcard_set_param_ac, "locality");
+    autocomplete_add(vcard_set_param_ac, "region");
+    autocomplete_add(vcard_set_param_ac, "pocode");
+    autocomplete_add(vcard_set_param_ac, "country");
+    autocomplete_add(vcard_set_param_ac, "type");
+    autocomplete_add(vcard_set_param_ac, "home");
+    autocomplete_add(vcard_set_param_ac, "work");
+    autocomplete_add(vcard_set_param_ac, "voice");
+    autocomplete_add(vcard_set_param_ac, "fax");
+    autocomplete_add(vcard_set_param_ac, "pager");
+    autocomplete_add(vcard_set_param_ac, "msg");
+    autocomplete_add(vcard_set_param_ac, "cell");
+    autocomplete_add(vcard_set_param_ac, "video");
+    autocomplete_add(vcard_set_param_ac, "bbs");
+    autocomplete_add(vcard_set_param_ac, "modem");
+    autocomplete_add(vcard_set_param_ac, "isdn");
+    autocomplete_add(vcard_set_param_ac, "pcs");
+    autocomplete_add(vcard_set_param_ac, "preferred");
+    autocomplete_add(vcard_set_param_ac, "x400");
+
+    vcard_togglable_param_ac = autocomplete_new();
+    autocomplete_add(vcard_togglable_param_ac, "home");
+    autocomplete_add(vcard_togglable_param_ac, "work");
+    autocomplete_add(vcard_togglable_param_ac, "voice");
+    autocomplete_add(vcard_togglable_param_ac, "fax");
+    autocomplete_add(vcard_togglable_param_ac, "pager");
+    autocomplete_add(vcard_togglable_param_ac, "msg");
+    autocomplete_add(vcard_togglable_param_ac, "cell");
+    autocomplete_add(vcard_togglable_param_ac, "video");
+    autocomplete_add(vcard_togglable_param_ac, "bbs");
+    autocomplete_add(vcard_togglable_param_ac, "modem");
+    autocomplete_add(vcard_togglable_param_ac, "isdn");
+    autocomplete_add(vcard_togglable_param_ac, "pcs");
+    autocomplete_add(vcard_togglable_param_ac, "preferred");
+    autocomplete_add(vcard_togglable_param_ac, "x400");
+
+    vcard_toggle_ac = autocomplete_new();
+    autocomplete_add(vcard_toggle_ac, "on");
+    autocomplete_add(vcard_toggle_ac, "off");
+
+    vcard_address_type_ac = autocomplete_new();
+    autocomplete_add(vcard_address_type_ac, "domestic");
+    autocomplete_add(vcard_address_type_ac, "international");
 }
 
 void
@@ -1502,6 +1596,17 @@ cmd_ac_reset(ProfWin* window)
     autocomplete_reset(mood_ac);
     autocomplete_reset(mood_type_ac);
     autocomplete_reset(adhoc_cmd_ac);
+
+    autocomplete_reset(vcard_ac);
+    autocomplete_reset(vcard_photo_ac);
+    autocomplete_reset(vcard_element_ac);
+    autocomplete_reset(vcard_set_ac);
+    autocomplete_reset(vcard_name_ac);
+    autocomplete_reset(vcard_set_param_ac);
+    autocomplete_reset(vcard_togglable_param_ac);
+    autocomplete_reset(vcard_toggle_ac);
+    autocomplete_reset(vcard_address_type_ac);
+
     autocomplete_reset(script_ac);
     autocomplete_reset(lastactivity_ac);
 
@@ -1672,6 +1777,15 @@ cmd_ac_uninit(void)
     autocomplete_free(intype_ac);
     autocomplete_free(adhoc_cmd_ac);
     autocomplete_free(lastactivity_ac);
+    autocomplete_free(vcard_ac);
+    autocomplete_free(vcard_photo_ac);
+    autocomplete_free(vcard_element_ac);
+    autocomplete_free(vcard_set_ac);
+    autocomplete_free(vcard_name_ac);
+    autocomplete_free(vcard_set_param_ac);
+    autocomplete_free(vcard_togglable_param_ac);
+    autocomplete_free(vcard_toggle_ac);
+    autocomplete_free(vcard_address_type_ac);
 }
 
 static void
@@ -1944,6 +2058,7 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
     g_hash_table_insert(ac_funcs, "/intype", _intype_autocomplete);
     g_hash_table_insert(ac_funcs, "/mood", _mood_autocomplete);
     g_hash_table_insert(ac_funcs, "/cmd", _adhoc_cmd_autocomplete);
+    g_hash_table_insert(ac_funcs, "/vcard", _vcard_autocomplete);
 
     int len = strlen(input);
     char parsed[len + 1];
@@ -4322,3 +4437,138 @@ _adhoc_cmd_autocomplete(ProfWin* window, const char* const input, gboolean previ
 
     return result;
 }
+
+static char*
+_vcard_autocomplete(ProfWin* window, const char* const input, gboolean previous)
+{
+    char* result = NULL;
+
+    gboolean parse_result = FALSE;
+    gchar** args = parse_args(input, 0, 7, &parse_result);
+
+    if (parse_result && (g_strcmp0(args[0], "set") == 0)) {
+        gboolean space_at_end = g_str_has_suffix(input, " ");
+        int num_args = g_strv_length(args);
+        gboolean is_num = TRUE;
+
+        if (num_args >= 2) {
+            for (int i = 0; i < strlen(args[1]); i++) {
+                if (!isdigit((int)args[1][i])) {
+                    is_num = FALSE;
+                    break;
+                }
+            }
+        }
+
+        if ((num_args == 2 && space_at_end && is_num) || (num_args == 3 && !space_at_end && is_num)) {
+            GString* beginning = g_string_new("/vcard");
+            g_string_append_printf(beginning, " %s %s", args[0], args[1]);
+            result = autocomplete_param_with_ac(input, beginning->str, vcard_set_param_ac, TRUE, previous);
+            g_string_free(beginning, TRUE);
+            if (result) {
+                g_strfreev(args);
+                return result;
+            }
+        } else if ((num_args == 3 && space_at_end && is_num && (g_strcmp0(args[2], "type") == 0)) || (num_args == 4 && !space_at_end && is_num && (g_strcmp0(args[2], "type") == 0))) {
+            GString* beginning = g_string_new("/vcard");
+            g_string_append_printf(beginning, " %s %s %s", args[0], args[1], args[2]);
+            result = autocomplete_param_with_ac(input, beginning->str, vcard_address_type_ac, TRUE, previous);
+            g_string_free(beginning, TRUE);
+            if (result) {
+                g_strfreev(args);
+                return result;
+            }
+        } else if ((num_args == 3 && space_at_end && is_num && autocomplete_contains(vcard_togglable_param_ac, args[2])) || (num_args == 4 && !space_at_end && is_num && autocomplete_contains(vcard_togglable_param_ac, args[2]))) {
+            GString* beginning = g_string_new("/vcard");
+            g_string_append_printf(beginning, " %s %s %s", args[0], args[1], args[2]);
+            result = autocomplete_param_with_ac(input, beginning->str, vcard_toggle_ac, TRUE, previous);
+            g_string_free(beginning, TRUE);
+            if (result) {
+                g_strfreev(args);
+                return result;
+            }
+        } else {
+            result = autocomplete_param_with_ac(input, "/vcard set name", vcard_name_ac, TRUE, previous);
+
+            if (result) {
+                return result;
+            }
+
+            result = autocomplete_param_with_ac(input, "/vcard set", vcard_set_ac, TRUE, previous);
+
+            if (result) {
+                return result;
+            }
+        }
+    }
+
+    result = autocomplete_param_with_ac(input, "/vcard add", vcard_element_ac, TRUE, previous);
+
+    if (result) {
+        return result;
+    }
+
+    if (window->type == WIN_MUC) {
+        char* unquoted = strip_arg_quotes(input);
+
+        ProfMucWin* mucwin = (ProfMucWin*)window;
+        assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+        Autocomplete nick_ac = muc_roster_ac(mucwin->roomjid);
+
+        if (nick_ac) {
+            result = autocomplete_param_with_ac(unquoted, "/vcard get", nick_ac, TRUE, previous);
+            if (result) {
+                free(unquoted);
+                return result;
+            }
+
+            result = autocomplete_param_with_ac(unquoted, "/vcard photo open", nick_ac, TRUE, previous);
+            if (result) {
+                free(unquoted);
+                return result;
+            }
+
+            result = autocomplete_param_with_ac(unquoted, "/vcard photo save", nick_ac, TRUE, previous);
+            if (result) {
+                free(unquoted);
+                return result;
+            }
+        }
+        free(unquoted);
+    } else {
+        char* unquoted = strip_arg_quotes(input);
+
+        result = autocomplete_param_with_func(unquoted, "/vcard get", roster_contact_autocomplete, previous, NULL);
+        if (result) {
+            free(unquoted);
+            return result;
+        }
+
+        result = autocomplete_param_with_func(unquoted, "/vcard photo open", roster_contact_autocomplete, previous, NULL);
+        if (result) {
+            free(unquoted);
+            return result;
+        }
+
+        result = autocomplete_param_with_func(unquoted, "/vcard photo save", roster_contact_autocomplete, previous, NULL);
+        if (result) {
+            free(unquoted);
+            return result;
+        }
+        free(unquoted);
+    }
+
+    result = autocomplete_param_with_ac(input, "/vcard photo", vcard_photo_ac, TRUE, previous);
+
+    if (result) {
+        return result;
+    }
+
+    result = autocomplete_param_with_ac(input, "/vcard", vcard_ac, TRUE, previous);
+
+    if (result) {
+        return result;
+    }
+
+    return result;
+}
diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c
index 91346fce..4263ac22 100644
--- a/src/command/cmd_defs.c
+++ b/src/command/cmd_defs.c
@@ -1268,7 +1268,8 @@ static struct cmd_t command_defs[] = {
               "/time all|console|chat|muc|config|private|xml off",
               "/time statusbar set <format>",
               "/time statusbar off",
-              "/time lastactivity set <format>")
+              "/time lastactivity set <format>",
+              "/time vcard set <format>")
       CMD_DESC(
               "Configure time display preferences. "
               "Time formats are strings supported by g_date_time_format. "
@@ -1291,7 +1292,8 @@ static struct cmd_t command_defs[] = {
               { "statusbar set <format>", "Change time format in statusbar." },
               { "statusbar off", "Do not show time in status bar." },
               { "lastactivity set <format>", "Change time format for last activity." },
-              { "all set <format>", "Set time for: console, chat, muc, config, private and xml windows." },
+              { "vcard set <format>", "Change the time format used to display time/dates in vCard (such as birthdays)" },
+              { "all set <format>", "Set time for: console, chat, muc, config, private, and xml windows." },
               { "all off", "Do not show time for: console, chat, muc, config, private and xml windows." })
       CMD_EXAMPLES(
               "/time console set %H:%M:%S",
@@ -1582,6 +1584,124 @@ static struct cmd_t command_defs[] = {
               "/autoconnect off")
     },
 
+    { "/vcard",
+      parse_args, 0, 7, NULL,
+      CMD_SUBFUNCS(
+              {"add", cmd_vcard_add},
+              {"remove", cmd_vcard_remove},
+              {"get", cmd_vcard_get},
+              {"set", cmd_vcard_set},
+              {"photo", cmd_vcard_photo},
+              {"refresh", cmd_vcard_refresh},
+              {"save", cmd_vcard_save})
+      CMD_MAINFUNC(cmd_vcard)
+      CMD_TAGS(
+              CMD_TAG_CHAT,
+              CMD_TAG_GROUPCHAT)
+      CMD_SYN(
+              "/vcard get [<nick|contact>]",
+              "/vcard photo open <nick|contact> [<index>]",
+              "/vcard photo save <nick|contact> [output <filepath>] [index <index>]",
+              "/vcard set fullname <fullname>",
+              "/vcard set name family <family>",
+              "/vcard set name given <given>",
+              "/vcard set name middle <middle>",
+              "/vcard set name prefix <prefix>",
+              "/vcard set name suffix <suffix>",
+              "/vcard set <index> [<value>]",
+              "/vcard set <index> pobox <value>",
+              "/vcard set <index> extaddr <value>",
+              "/vcard set <index> street <value>",
+              "/vcard set <index> locality <value>",
+              "/vcard set <index> region <value>",
+              "/vcard set <index> pocode <value>",
+              "/vcard set <index> country <value>",
+              "/vcard set <index> type domestic|international",
+              "/vcard set <index> home on|off",
+              "/vcard set <index> work on|off",
+              "/vcard set <index> voice on|off",
+              "/vcard set <index> fax on|off",
+              "/vcard set <index> pager on|off",
+              "/vcard set <index> msg on|off",
+              "/vcard set <index> cell on|off",
+              "/vcard set <index> video on|off",
+              "/vcard set <index> bbs on|off",
+              "/vcard set <index> modem on|off",
+              "/vcard set <index> isdn on|off",
+              "/vcard set <index> pcs on|off",
+              "/vcard set <index> preferred on|off",
+              "/vcard set <index> parcel on|off",
+              "/vcard set <index> postal on|off",
+              "/vcard set <index> internet on|off",
+              "/vcard set <index> x400 on|off",
+              "/vcard add nickname <nickname>",
+              "/vcard add birthday <date>",
+              "/vcard add address",
+              "/vcard add tel <number>",
+              "/vcard add email <userid>",
+              "/vcard add jid <jid>",
+              "/vcard add title <title>",
+              "/vcard add role <role>",
+              "/vcard add note <note>",
+              "/vcard add url <url>",
+              "/vcard remove <index>",
+              "/vcard refresh",
+              "/vcard save")
+      CMD_DESC(
+              "Read your vCard or a user's vCard, get a user's avatar via their vCard, or modify your vCard. If no arguments are given, your vCard will be displayed in a new window, or an existing vCard window.")
+      CMD_ARGS(
+              { "get [<nick|contact>]", "Get your vCard, if a nickname/contact is provided, get that user's vCard" },
+              { "photo open <nick|contact> [<index>]", "Download a user's photo from their vCard to a file, and open it. If index is not specified, download the first photo (usually avatar) from their vCard" },
+              { "photo save <nick|contact>", "Download a user's photo from their vCard to a file. If index is not specified, download the first photo (usually avatar) from their vCard. If output is not specified, download the photo to profanity's photos directory." },
+              { "photo open-self [<index>]", "Download a photo from your vCard to a file, and open it. If index is not specified, download the first photo (usually avatar) from your vCard" },
+              { "photo save-self", "Download a photo from your vCard to a file. If index is not specified, download the first photo (usually avatar) from your vCard. If output is not specified, download the photo to profanity's photos directory. Same arguments as `photo open`" },
+              { "set fullname <fullname>", "Set your vCard's fullname to the specified value" },
+              { "set name family <family>", "Set your vCard's family name to the specified value" },
+              { "set name given <given>", "Set your vCard's given name to the specified value" },
+              { "set name middle <middle>", "Set your vCard's middle name to the specified value" },
+              { "set name prefix <prefix>", "Set your vCard's prefix name to the specified value" },
+              { "set name suffix <suffix>", "Set your vCard's suffix name to the specified value" },
+              { "set <index> [<value>]", "Set the main field in a element in your vCard to the specified value, or if no value was specified, modify the field in an editor, This only works in elements that have one field." },
+              { "set <index> pobox <value>", "Set the P.O. box in an address element in your vCard to the specified value." },
+              { "set <index> extaddr <value>", "Set the extended address in an address element in your vCard to the specified value." },
+              { "set <index> street <value>", "Set the street in an address element in your vCard to the specified value." },
+              { "set <index> locality <value>", "Set the locality in an address element in your vCard to the specified value." },
+              { "set <index> region <value>", "Set the region in an address element in your vCard to the specified value." },
+              { "set <index> pocode <value>", "Set the P.O. code in an address element in your vCard to the specified value." },
+              { "set <index> type domestic|international", "Set the type in an address element in your vCard to either domestic or international." },
+              { "set <index> home on|off", "Set the home option in an element in your vCard. (address, telephone, e-mail only)" },
+              { "set <index> work on|off", "Set the work option in an element in your vCard. (address, telephone, e-mail only)" },
+              { "set <index> voice on|off", "Set the voice option in a telephone element in your vCard." },
+              { "set <index> fax on|off", "Set the fax option in a telephone element in your vCard." },
+              { "set <index> pager on|off", "Set the pager option in a telephone element in your vCard." },
+              { "set <index> msg on|off", "Set the message option in a telephone element in your vCard." },
+              { "set <index> cell on|off", "Set the cellphone option in a telephone element in your vCard." },
+              { "set <index> video on|off", "Set the video option in a telephone element in your vCard." },
+              { "set <index> bbs on|off", "Set the BBS option in a telephone element in your vCard." },
+              { "set <index> modem on|off", "Set the modem option in a telephone element in your vCard." },
+              { "set <index> isdn on|off", "Set the ISDN option in a telephone element in your vCard." },
+              { "set <index> pcs on|off", "Set the PCS option in a telephone element in your vCard." },
+              { "set <index> preferred on|off", "Set the preferred option in an element in your vCard. (address, telephone, e-mail only)" },
+              { "set <index> parcel on|off", "Set the parcel option in an address element in your vCard." },
+              { "set <index> postal on|off", "Set the postal option in an address element in your vCard." },
+              { "set <index> internet on|off", "Set the internet option in an e-mail address in your vCard." },
+              { "set <index> x400 on|off", "Set the X400 option in an e-mail address in your vCard." },
+              { "add nickname <nickname>", "Add a nickname to your vCard" },
+              { "add birthday <date>", "Add a birthday date to your vCard" },
+              { "add address", "Add an address to your vCard" },
+              { "add tel <number>", "Add a telephone number to your vCard" },
+              { "add email <userid>", "Add an e-mail address to your vCard" },
+              { "add jid <jid>", "Add a Jabber ID to your vCard" },
+              { "add title <title>", "Add a title to your vCard" },
+              { "add role <role>", "Add a role to your vCard" },
+              { "add note <note>", "Add a note to your vCard" },
+              { "add url <url>", "Add a URL to your vCard" },
+              { "remove <index>", "Remove a element in your vCard by index" },
+              { "refresh", "Refreshes the local copy of the current account's vCard (undoes all your unpublished modifications)" },
+              { "save", "Save changes to the server" })
+      CMD_NOEXAMPLES
+    },
+
     { "/vercheck",
       parse_args, 0, 1, NULL,
       CMD_NOSUBFUNCS
@@ -2559,7 +2679,8 @@ static struct cmd_t command_defs[] = {
               { "avatar",  cmd_executable_avatar },
               { "urlopen", cmd_executable_urlopen },
               { "urlsave", cmd_executable_urlsave },
-              { "editor", cmd_executable_editor })
+              { "editor", cmd_executable_editor },
+              { "vcard_photo", cmd_executable_vcard_photo })
       CMD_NOMAINFUNC
       CMD_TAGS(
               CMD_TAG_DISCOVERY)
@@ -2568,7 +2689,9 @@ static struct cmd_t command_defs[] = {
               "/executable urlopen set <cmdtemplate>",
               "/executable urlopen default",
               "/executable urlsave set <cmdtemplate>",
-              "/executable urlsave default")
+              "/executable urlsave default",
+              "/executable vcard_photo set <cmdtemplate>",
+              "/executable vcard_photo default")
       CMD_DESC(
               "Configure executable that should be called upon a certain command.")
       CMD_ARGS(
@@ -2577,7 +2700,9 @@ static struct cmd_t command_defs[] = {
               { "urlopen default", "Restore to default settings." },
               { "urlsave set", "Set executable that is run by /url save. Takes a command template that replaces %u and %p with the URL and path respectively." },
               { "urlsave default", "Use the built-in download method for saving." },
-              { "editor set", "Set editor to be used with /editor. Needs a terminal editor or a script to run a graphical editor." })
+              { "editor set", "Set editor to be used with /editor. Needs a terminal editor or a script to run a graphical editor." },
+              { "vcard_photo set", "Set executable that is run by /vcard photo open. Takes a command template that replaces %p with the path" },
+              { "vcard_photo default", "Restore to default settings." })
       CMD_EXAMPLES(
               "/executable avatar xdg-open",
               "/executable urlopen set \"xdg-open %u\"",
@@ -2586,6 +2711,7 @@ static struct cmd_t command_defs[] = {
               "/executable urlsave set \"wget %u -O %p\"",
               "/executable urlsave set \"curl %u -o %p\"",
               "/executable urlsave default",
+              "/executable vcard_photo set \"feh %p\"",
               "/executable editor set vim")
     },
 
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index 9978e889..420bc0f6 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -91,6 +91,7 @@
 #include "xmpp/chat_session.h"
 #include "xmpp/avatar.h"
 #include "xmpp/stanza.h"
+#include "xmpp/vcard_funcs.h"
 
 #ifdef HAVE_LIBOTR
 #include "otr/otr.h"
@@ -5580,6 +5581,25 @@ cmd_time(ProfWin* window, const char* const command, gchar** args)
             cons_bad_cmd_usage(command);
             return TRUE;
         }
+    } else if (g_strcmp0(args[0], "vcard") == 0) {
+        if (args[1] == NULL) {
+            char* format = prefs_get_string(PREF_TIME_VCARD);
+            cons_show("vCard time format: %s", format);
+            g_free(format);
+            return TRUE;
+        } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
+            prefs_set_string(PREF_TIME_VCARD, args[2]);
+            cons_show("vCard time format set to '%s'.", args[2]);
+            ui_redraw();
+            return TRUE;
+        } else if (g_strcmp0(args[1], "off") == 0) {
+            cons_show("vCard time cannot be disabled.");
+            ui_redraw();
+            return TRUE;
+        } else {
+            cons_bad_cmd_usage(command);
+            return TRUE;
+        }
     } else {
         cons_bad_cmd_usage(command);
         return TRUE;
@@ -9639,6 +9659,23 @@ cmd_executable_editor(ProfWin* window, const char* const command, gchar** args)
 }
 
 gboolean
+cmd_executable_vcard_photo(ProfWin* window, const char* const command, gchar** args)
+{
+    if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
+        prefs_set_string(PREF_VCARD_PHOTO_CMD, args[2]);
+        cons_show("`vcard photo open` command set to invoke '%s'", args[2]);
+    } else if (g_strcmp0(args[1], "default") == 0) {
+        prefs_set_string(PREF_VCARD_PHOTO_CMD, NULL);
+        char* cmd = prefs_get_string(PREF_VCARD_PHOTO_CMD);
+        cons_show("`vcard photo open` command set to invoke '%s' (default)", cmd);
+        g_free(cmd);
+    } else {
+        cons_bad_cmd_usage(command);
+    }
+
+    return TRUE;
+}
+gboolean
 cmd_mam(ProfWin* window, const char* const command, gchar** args)
 {
     _cmd_set_boolean_preference(args[0], command, "Message Archive Management", PREF_MAM);
@@ -9818,3 +9855,956 @@ cmd_mood(ProfWin* window, const char* const command, gchar** args)
 
     return TRUE;
 }
+
+gboolean
+cmd_vcard(ProfWin* window, const char* const command, gchar** args)
+{
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You are not currently connected.");
+        return TRUE;
+    }
+
+    ProfVcardWin* vcardwin = wins_get_vcard();
+
+    if (vcardwin) {
+        ui_focus_win((ProfWin*)vcardwin);
+    } else {
+        vcardwin = (ProfVcardWin*)vcard_user_create_win();
+        ui_focus_win((ProfWin*)vcardwin);
+    }
+    vcardwin_update();
+    return TRUE;
+}
+
+gboolean
+cmd_vcard_add(ProfWin* window, const char* const command, gchar** args)
+{
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You are not currently connected.");
+        return TRUE;
+    }
+
+    vcard_element_t* element = calloc(1, sizeof(vcard_element_t));
+    if (!element) {
+        cons_show_error("Memory allocation failed.");
+        return TRUE;
+    }
+
+    struct tm tm;
+    gchar* type = args[1];
+    gchar* value = args[2];
+
+    if (g_strcmp0(type, "nickname") == 0) {
+        element->type = VCARD_NICKNAME;
+
+        element->nickname = strdup(value);
+    } else if (g_strcmp0(type, "birthday") == 0) {
+        element->type = VCARD_BIRTHDAY;
+
+        memset(&tm, 0, sizeof(struct tm));
+        if (!strptime(value, "%Y-%m-%d", &tm)) {
+            cons_show_error("Error parsing ISO8601 date.");
+            free(element);
+            return TRUE;
+        }
+        element->birthday = g_date_time_new_local(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 0, 0, 0);
+    } else if (g_strcmp0(type, "tel") == 0) {
+        element->type = VCARD_TELEPHONE;
+        if (value) {
+            element->telephone.number = strdup(value);
+        }
+    } else if (g_strcmp0(type, "address") == 0) {
+        element->type = VCARD_ADDRESS;
+    } else if (g_strcmp0(type, "email") == 0) {
+        element->type = VCARD_EMAIL;
+        if (value) {
+            element->email.userid = strdup(value);
+        }
+    } else if (g_strcmp0(type, "jid") == 0) {
+        element->type = VCARD_JID;
+        if (value) {
+            element->jid = strdup(value);
+        }
+    } else if (g_strcmp0(type, "title") == 0) {
+        element->type = VCARD_TITLE;
+        if (value) {
+            element->title = strdup(value);
+        }
+    } else if (g_strcmp0(type, "role") == 0) {
+        element->type = VCARD_ROLE;
+        if (value) {
+            element->role = strdup(value);
+        }
+    } else if (g_strcmp0(type, "note") == 0) {
+        element->type = VCARD_NOTE;
+        if (value) {
+            element->note = strdup(value);
+        }
+    } else if (g_strcmp0(type, "url") == 0) {
+        element->type = VCARD_URL;
+        if (value) {
+            element->url = strdup(value);
+        }
+    } else {
+        cons_bad_cmd_usage(command);
+        free(element);
+        return TRUE;
+    }
+
+    vcard_user_add_element(element);
+    vcardwin_update();
+    return TRUE;
+}
+
+gboolean
+cmd_vcard_remove(ProfWin* window, const char* const command, gchar** args)
+{
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You are not currently connected.");
+        return TRUE;
+    }
+
+    if (args[1]) {
+        vcard_user_remove_element(atoi(args[1]));
+        cons_show("Removed element at index %d", atoi(args[1]));
+        vcardwin_update();
+    } else {
+        cons_bad_cmd_usage(command);
+    }
+    return TRUE;
+}
+
+gboolean
+cmd_vcard_get(ProfWin* window, const char* const command, gchar** args)
+{
+    char* user = args[1];
+    xmpp_ctx_t* const ctx = connection_get_ctx();
+
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You are not currently connected.");
+        return TRUE;
+    }
+
+    if (user) {
+        // get the JID when in MUC window
+        if (window->type == WIN_MUC) {
+            ProfMucWin* mucwin = (ProfMucWin*)window;
+            assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+
+            if (muc_anonymity_type(mucwin->roomjid) == MUC_ANONYMITY_TYPE_NONANONYMOUS) {
+                // non-anon muc: get the user's jid and send vcard request to them
+                Occupant* occupant = muc_roster_item(mucwin->roomjid, user);
+                Jid* jid_occupant = jid_create(occupant->jid);
+
+                vcard_print(ctx, window, jid_occupant->barejid);
+                jid_destroy(jid_occupant);
+            } else {
+                // anon muc: send the vcard request through the MUC's server
+                GString* full_jid = g_string_new(mucwin->roomjid);
+                g_string_append(full_jid, "/");
+                g_string_append(full_jid, user);
+
+                vcard_print(ctx, window, full_jid->str);
+
+                g_string_free(full_jid, TRUE);
+            }
+        } else {
+            char* jid = roster_barejid_from_name(user);
+            if (!jid) {
+                cons_bad_cmd_usage(command);
+                return TRUE;
+            }
+
+            vcard_print(ctx, window, jid);
+        }
+    } else {
+        if (window->type == WIN_CHAT) {
+            ProfChatWin* chatwin = (ProfChatWin*)window;
+            assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+
+            vcard_print(ctx, window, chatwin->barejid);
+        } else {
+            vcard_print(ctx, window, NULL);
+        }
+    }
+
+    return TRUE;
+}
+
+gboolean
+cmd_vcard_photo(ProfWin* window, const char* const command, gchar** args)
+{
+    char* operation = args[1];
+    char* user = args[2];
+
+    xmpp_ctx_t* const ctx = connection_get_ctx();
+
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You are not currently connected.");
+        return TRUE;
+    }
+
+    gboolean jidless = (g_strcmp0(operation, "open-self") == 0 || g_strcmp0(operation, "save-self") == 0);
+
+    if (!operation || (!jidless && !user)) {
+        cons_bad_cmd_usage(command);
+        return TRUE;
+    }
+
+    char* jid = NULL;
+    char* filepath = NULL;
+    int index = 0;
+
+    if (!jidless) {
+        if (window->type == WIN_MUC) {
+            ProfMucWin* mucwin = (ProfMucWin*)window;
+            assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+
+            if (muc_anonymity_type(mucwin->roomjid) == MUC_ANONYMITY_TYPE_NONANONYMOUS) {
+                // non-anon muc: get the user's jid and send vcard request to them
+                Occupant* occupant = muc_roster_item(mucwin->roomjid, user);
+                Jid* jid_occupant = jid_create(occupant->jid);
+
+                jid = g_strdup(jid_occupant->barejid);
+                jid_destroy(jid_occupant);
+            } else {
+                // anon muc: send the vcard request through the MUC's server
+                GString* full_jid = g_string_new(mucwin->roomjid);
+                g_string_append(full_jid, "/");
+                g_string_append(full_jid, user);
+
+                jid = full_jid->str;
+
+                g_string_free(full_jid, FALSE);
+            }
+        } else {
+            char* jid_temp = roster_barejid_from_name(user);
+            if (!jid_temp) {
+                cons_bad_cmd_usage(command);
+                return TRUE;
+            } else {
+                jid = g_strdup(jid_temp);
+            }
+        }
+    }
+    if (!g_strcmp0(operation, "open")) {
+        // if an index is provided
+        if (args[3]) {
+            vcard_photo(ctx, jid, NULL, atoi(args[3]), TRUE);
+        } else {
+            vcard_photo(ctx, jid, NULL, -1, TRUE);
+        }
+    } else if (!g_strcmp0(operation, "save")) {
+        // arguments
+        if (g_strv_length(args) > 2) {
+            gchar* opt_keys[] = { "output", "index", NULL };
+            gboolean parsed;
+
+            GHashTable* options = parse_options(&args[3], opt_keys, &parsed);
+            if (!parsed) {
+                cons_bad_cmd_usage(command);
+                options_destroy(options);
+                return TRUE;
+            }
+
+            filepath = g_hash_table_lookup(options, "output");
+            if (!filepath) {
+                filepath = NULL;
+            }
+
+            char* index_str = g_hash_table_lookup(options, "index");
+            if (!index_str) {
+                index = -1;
+            } else {
+                index = atoi(index_str);
+            }
+
+            options_destroy(options);
+        } else {
+            filepath = NULL;
+            index = -1;
+        }
+
+        vcard_photo(ctx, jid, filepath, index, FALSE);
+    } else if (!g_strcmp0(operation, "open-self")) {
+        // if an index is provided
+        if (args[2]) {
+            vcard_photo(ctx, NULL, NULL, atoi(args[2]), TRUE);
+        } else {
+            vcard_photo(ctx, NULL, NULL, -1, TRUE);
+        }
+    } else if (!g_strcmp0(operation, "save-self")) {
+        // arguments
+        if (g_strv_length(args) > 2) {
+            gchar* opt_keys[] = { "output", "index", NULL };
+            gboolean parsed;
+
+            GHashTable* options = parse_options(&args[2], opt_keys, &parsed);
+            if (!parsed) {
+                cons_bad_cmd_usage(command);
+                options_destroy(options);
+                return TRUE;
+            }
+
+            filepath = g_hash_table_lookup(options, "output");
+            if (!filepath) {
+                filepath = NULL;
+            }
+
+            char* index_str = g_hash_table_lookup(options, "index");
+            if (!index_str) {
+                index = -1;
+            } else {
+                index = atoi(index_str);
+            }
+
+            options_destroy(options);
+        } else {
+            filepath = NULL;
+            index = -1;
+        }
+
+        vcard_photo(ctx, NULL, filepath, index, FALSE);
+    } else {
+        cons_bad_cmd_usage(command);
+    }
+
+    if (!jidless) {
+        g_free(jid);
+    }
+    return TRUE;
+}
+
+gboolean
+cmd_vcard_refresh(ProfWin* window, const char* const command, gchar** args)
+{
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You are not currently connected.");
+        return TRUE;
+    }
+
+    vcard_user_refresh();
+    vcardwin_update();
+    return TRUE;
+}
+
+gboolean
+cmd_vcard_set(ProfWin* window, const char* const command, gchar** args)
+{
+    char* key = args[1];
+    char* value = args[2];
+
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You are not currently connected.");
+        return TRUE;
+    }
+
+    if (!key) {
+        cons_bad_cmd_usage(command);
+        return TRUE;
+    }
+
+    gboolean is_num = TRUE;
+    for (int i = 0; i < strlen(key); i++) {
+        if (!isdigit((int)key[i])) {
+            is_num = FALSE;
+            break;
+        }
+    }
+
+    if (g_strcmp0(key, "fullname") == 0 && value) {
+        vcard_user_set_fullname(value);
+        cons_show("User vCard's full name has been set");
+    } else if (g_strcmp0(key, "name") == 0 && value) {
+        char* value2 = args[3];
+
+        if (!value2) {
+            cons_bad_cmd_usage(command);
+            return TRUE;
+        }
+
+        if (g_strcmp0(value, "family") == 0) {
+            vcard_user_set_name_family(value2);
+            cons_show("User vCard's family name has been set");
+        } else if (g_strcmp0(value, "given") == 0) {
+            vcard_user_set_name_given(value2);
+            cons_show("User vCard's given name has been set");
+        } else if (g_strcmp0(value, "middle") == 0) {
+            vcard_user_set_name_middle(value2);
+            cons_show("User vCard's middle name has been set");
+        } else if (g_strcmp0(value, "prefix") == 0) {
+            vcard_user_set_name_prefix(value2);
+            cons_show("User vCard's prefix name has been set");
+        } else if (g_strcmp0(value, "suffix") == 0) {
+            vcard_user_set_name_suffix(value2);
+            cons_show("User vCard's suffix name has been set");
+        }
+    } else if (is_num) {
+        char* value2 = args[3];
+        struct tm tm;
+
+        vcard_element_t* element = vcard_user_get_element_index(atoi(key));
+
+        if (!element) {
+            cons_bad_cmd_usage(command);
+            return TRUE;
+        }
+
+        if (!value2 || !value) {
+            // Set the main field of element at index <key> to <value>, or from an editor
+
+            switch (element->type) {
+            case VCARD_NICKNAME:
+                if (!value) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->nickname, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->nickname) {
+                        free(element->nickname);
+                    }
+                    element->nickname = editor_value;
+                } else {
+                    if (element->nickname) {
+                        free(element->nickname);
+                    }
+                    element->nickname = strdup(value);
+                }
+                break;
+            case VCARD_BIRTHDAY:
+                memset(&tm, 0, sizeof(struct tm));
+                if (!strptime(value, "%Y-%m-%d", &tm)) {
+                    cons_show_error("Error parsing ISO8601 date.");
+                    return TRUE;
+                }
+
+                if (element->birthday) {
+                    g_date_time_unref(element->birthday);
+                }
+                element->birthday = g_date_time_new_local(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 0, 0, 0);
+                break;
+            case VCARD_TELEPHONE:
+                if (!value) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->telephone.number, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->telephone.number) {
+                        free(element->telephone.number);
+                    }
+                    element->telephone.number = editor_value;
+                } else {
+                    if (element->telephone.number) {
+                        free(element->telephone.number);
+                    }
+                    element->telephone.number = strdup(value);
+                }
+
+                break;
+            case VCARD_EMAIL:
+                if (!value) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->email.userid, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->email.userid) {
+                        free(element->email.userid);
+                    }
+                    element->email.userid = editor_value;
+                } else {
+                    if (element->email.userid) {
+                        free(element->email.userid);
+                    }
+                    element->email.userid = strdup(value);
+                }
+                break;
+            case VCARD_JID:
+                if (!value) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->jid, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->jid) {
+                        free(element->jid);
+                    }
+                    element->jid = editor_value;
+                } else {
+                    if (element->jid) {
+                        free(element->jid);
+                    }
+                    element->jid = strdup(value);
+                }
+                break;
+            case VCARD_TITLE:
+                if (!value) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->title, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->title) {
+                        free(element->title);
+                    }
+                    element->title = editor_value;
+                } else {
+                    if (element->title) {
+                        free(element->title);
+                    }
+                    element->title = strdup(value);
+                }
+                break;
+            case VCARD_ROLE:
+                if (!value) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->role, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->role) {
+                        free(element->role);
+                    }
+                    element->role = editor_value;
+                } else {
+                    if (element->role) {
+                        free(element->role);
+                    }
+                    element->role = strdup(value);
+                }
+                break;
+            case VCARD_NOTE:
+                if (!value) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->note, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->note) {
+                        free(element->note);
+                    }
+                    element->note = editor_value;
+                } else {
+                    if (element->note) {
+                        free(element->note);
+                    }
+                    element->note = strdup(value);
+                }
+                break;
+            case VCARD_URL:
+                if (!value) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->url, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->url) {
+                        free(element->url);
+                    }
+                    element->url = editor_value;
+                } else {
+                    if (element->url) {
+                        free(element->url);
+                    }
+                    element->url = strdup(value);
+                }
+                break;
+            default:
+                cons_show_error("Element unsupported");
+            }
+        } else if (value) {
+            if (g_strcmp0(value, "pobox") == 0 && element->type == VCARD_ADDRESS) {
+                if (!value2) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->address.pobox, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->address.pobox) {
+                        free(element->address.pobox);
+                    }
+                    element->address.pobox = editor_value;
+                } else {
+                    if (element->address.pobox) {
+                        free(element->address.pobox);
+                    }
+                    element->address.pobox = strdup(value2);
+                }
+            } else if (g_strcmp0(value, "extaddr") == 0 && element->type == VCARD_ADDRESS) {
+                if (!value2) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->address.extaddr, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->address.extaddr) {
+                        free(element->address.extaddr);
+                    }
+                    element->address.extaddr = editor_value;
+                } else {
+                    if (element->address.extaddr) {
+                        free(element->address.extaddr);
+                    }
+                    element->address.extaddr = strdup(value2);
+                }
+            } else if (g_strcmp0(value, "street") == 0 && element->type == VCARD_ADDRESS) {
+                if (!value2) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->address.street, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->address.street) {
+                        free(element->address.street);
+                    }
+                    element->address.street = editor_value;
+                } else {
+                    if (element->address.street) {
+                        free(element->address.street);
+                    }
+                    element->address.street = strdup(value2);
+                }
+            } else if (g_strcmp0(value, "locality") == 0 && element->type == VCARD_ADDRESS) {
+                if (!value2) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->address.locality, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->address.locality) {
+                        free(element->address.locality);
+                    }
+                    element->address.locality = editor_value;
+                } else {
+                    if (element->address.locality) {
+                        free(element->address.locality);
+                    }
+                    element->address.locality = strdup(value2);
+                }
+            } else if (g_strcmp0(value, "region") == 0 && element->type == VCARD_ADDRESS) {
+                if (!value2) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->address.region, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->address.region) {
+                        free(element->address.region);
+                    }
+                    element->address.region = editor_value;
+                } else {
+                    if (element->address.region) {
+                        free(element->address.region);
+                    }
+                    element->address.region = strdup(value2);
+                }
+            } else if (g_strcmp0(value, "pocode") == 0 && element->type == VCARD_ADDRESS) {
+                if (!value2) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->address.pcode, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->address.pcode) {
+                        free(element->address.pcode);
+                    }
+                    element->address.pcode = editor_value;
+                } else {
+                    if (element->address.pcode) {
+                        free(element->address.pcode);
+                    }
+                    element->address.pcode = strdup(value2);
+                }
+            } else if (g_strcmp0(value, "country") == 0 && element->type == VCARD_ADDRESS) {
+                if (!value2) {
+                    gchar* editor_value;
+                    if (get_message_from_editor(element->address.country, &editor_value)) {
+                        return TRUE;
+                    }
+
+                    if (element->address.country) {
+                        free(element->address.country);
+                    }
+                    element->address.country = editor_value;
+                } else {
+                    if (element->address.country) {
+                        free(element->address.country);
+                    }
+                    element->address.country = strdup(value2);
+                }
+            } else if (g_strcmp0(value, "type") == 0 && element->type == VCARD_ADDRESS) {
+                if (g_strcmp0(value2, "domestic") == 0) {
+                    element->address.options &= ~VCARD_INTL;
+                    element->address.options |= VCARD_DOM;
+                } else if (g_strcmp0(value2, "international") == 0) {
+                    element->address.options &= ~VCARD_DOM;
+                    element->address.options |= VCARD_INTL;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "home") == 0) {
+                switch (element->type) {
+                case VCARD_ADDRESS:
+                    if (g_strcmp0(value2, "on") == 0) {
+                        element->address.options |= VCARD_HOME;
+                    } else if (g_strcmp0(value2, "off") == 0) {
+                        element->address.options &= ~VCARD_HOME;
+                    } else {
+                        cons_bad_cmd_usage(command);
+                        return TRUE;
+                    }
+                    break;
+                case VCARD_TELEPHONE:
+                    if (g_strcmp0(value2, "on") == 0) {
+                        element->telephone.options |= VCARD_HOME;
+                    } else if (g_strcmp0(value2, "off") == 0) {
+                        element->telephone.options &= ~VCARD_HOME;
+                    } else {
+                        cons_bad_cmd_usage(command);
+                        return TRUE;
+                    }
+                    break;
+                case VCARD_EMAIL:
+                    if (g_strcmp0(value2, "on") == 0) {
+                        element->email.options |= VCARD_HOME;
+                    } else if (g_strcmp0(value2, "off") == 0) {
+                        element->email.options &= ~VCARD_HOME;
+                    } else {
+                        cons_bad_cmd_usage(command);
+                        return TRUE;
+                    }
+                    break;
+                default:
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "work") == 0) {
+                switch (element->type) {
+                case VCARD_ADDRESS:
+                    if (g_strcmp0(value2, "on") == 0) {
+                        element->address.options |= VCARD_WORK;
+                    } else if (g_strcmp0(value2, "off") == 0) {
+                        element->address.options &= ~VCARD_WORK;
+                    } else {
+                        cons_bad_cmd_usage(command);
+                        return TRUE;
+                    }
+                    break;
+                case VCARD_TELEPHONE:
+                    if (g_strcmp0(value2, "on") == 0) {
+                        element->telephone.options |= VCARD_WORK;
+                    } else if (g_strcmp0(value2, "off") == 0) {
+                        element->telephone.options &= ~VCARD_WORK;
+                    } else {
+                        cons_bad_cmd_usage(command);
+                        return TRUE;
+                    }
+                    break;
+                case VCARD_EMAIL:
+                    if (g_strcmp0(value2, "on") == 0) {
+                        element->email.options |= VCARD_WORK;
+                    } else if (g_strcmp0(value2, "off") == 0) {
+                        element->email.options &= ~VCARD_WORK;
+                    } else {
+                        cons_bad_cmd_usage(command);
+                        return TRUE;
+                    }
+                    break;
+                default:
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "voice") == 0 && element->type == VCARD_TELEPHONE) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->telephone.options |= VCARD_TEL_VOICE;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->telephone.options &= ~VCARD_TEL_VOICE;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "fax") == 0 && element->type == VCARD_TELEPHONE) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->telephone.options |= VCARD_TEL_FAX;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->telephone.options &= ~VCARD_TEL_FAX;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "pager") == 0 && element->type == VCARD_TELEPHONE) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->telephone.options |= VCARD_TEL_PAGER;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->telephone.options &= ~VCARD_TEL_PAGER;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "msg") == 0 && element->type == VCARD_TELEPHONE) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->telephone.options |= VCARD_TEL_MSG;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->telephone.options &= ~VCARD_TEL_MSG;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "cell") == 0 && element->type == VCARD_TELEPHONE) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->telephone.options |= VCARD_TEL_CELL;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->telephone.options &= ~VCARD_TEL_CELL;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "video") == 0 && element->type == VCARD_TELEPHONE) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->telephone.options |= VCARD_TEL_VIDEO;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->telephone.options &= ~VCARD_TEL_VIDEO;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "bbs") == 0 && element->type == VCARD_TELEPHONE) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->telephone.options |= VCARD_TEL_BBS;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->telephone.options &= ~VCARD_TEL_BBS;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "modem") == 0 && element->type == VCARD_TELEPHONE) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->telephone.options |= VCARD_TEL_MODEM;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->telephone.options &= ~VCARD_TEL_MODEM;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "isdn") == 0 && element->type == VCARD_TELEPHONE) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->telephone.options |= VCARD_TEL_ISDN;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->telephone.options &= ~VCARD_TEL_ISDN;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "pcs") == 0 && element->type == VCARD_TELEPHONE) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->telephone.options |= VCARD_TEL_PCS;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->telephone.options &= ~VCARD_TEL_PCS;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "preferred") == 0) {
+                switch (element->type) {
+                case VCARD_ADDRESS:
+                    if (g_strcmp0(value2, "on") == 0) {
+                        element->address.options |= VCARD_PREF;
+                    } else if (g_strcmp0(value2, "off") == 0) {
+                        element->address.options &= ~VCARD_PREF;
+                    } else {
+                        cons_bad_cmd_usage(command);
+                        return TRUE;
+                    }
+                    break;
+                case VCARD_TELEPHONE:
+                    if (g_strcmp0(value2, "on") == 0) {
+                        element->telephone.options |= VCARD_PREF;
+                    } else if (g_strcmp0(value2, "off") == 0) {
+                        element->telephone.options &= ~VCARD_PREF;
+                    } else {
+                        cons_bad_cmd_usage(command);
+                        return TRUE;
+                    }
+                    break;
+                case VCARD_EMAIL:
+                    if (g_strcmp0(value2, "on") == 0) {
+                        element->email.options |= VCARD_PREF;
+                    } else if (g_strcmp0(value2, "off") == 0) {
+                        element->email.options &= ~VCARD_PREF;
+                    } else {
+                        cons_bad_cmd_usage(command);
+                        return TRUE;
+                    }
+                    break;
+                default:
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "parcel") == 0 && element->type == VCARD_ADDRESS) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->address.options |= VCARD_PARCEL;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->address.options &= ~VCARD_PARCEL;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "postal") == 0 && element->type == VCARD_ADDRESS) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->address.options |= VCARD_POSTAL;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->address.options &= ~VCARD_POSTAL;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "internet") == 0 && element->type == VCARD_EMAIL) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->email.options |= VCARD_EMAIL_INTERNET;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->email.options &= ~VCARD_EMAIL_INTERNET;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else if (g_strcmp0(value, "x400") == 0 && element->type == VCARD_EMAIL) {
+                if (g_strcmp0(value2, "on") == 0) {
+                    element->email.options |= VCARD_EMAIL_X400;
+                } else if (g_strcmp0(value2, "off") == 0) {
+                    element->email.options &= ~VCARD_EMAIL_X400;
+                } else {
+                    cons_bad_cmd_usage(command);
+                    return TRUE;
+                }
+            } else {
+                cons_bad_cmd_usage(command);
+                return TRUE;
+            }
+        } else {
+            cons_bad_cmd_usage(command);
+            return TRUE;
+        }
+    } else {
+        cons_bad_cmd_usage(command);
+        return TRUE;
+    }
+
+    vcardwin_update();
+    return TRUE;
+}
+
+gboolean
+cmd_vcard_save(ProfWin* window, const char* const command, gchar** args)
+{
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You are not currently connected.");
+        return TRUE;
+    }
+
+    vcard_user_save();
+    cons_show("User vCard uploaded");
+    return TRUE;
+}
diff --git a/src/command/cmd_funcs.h b/src/command/cmd_funcs.h
index adc6793d..83594bdd 100644
--- a/src/command/cmd_funcs.h
+++ b/src/command/cmd_funcs.h
@@ -246,6 +246,7 @@ gboolean cmd_executable_avatar(ProfWin* window, const char* const command, gchar
 gboolean cmd_executable_urlopen(ProfWin* window, const char* const command, gchar** args);
 gboolean cmd_executable_urlsave(ProfWin* window, const char* const command, gchar** args);
 gboolean cmd_executable_editor(ProfWin* window, const char* const command, gchar** args);
+gboolean cmd_executable_vcard_photo(ProfWin* window, const char* const command, gchar** args);
 gboolean cmd_mam(ProfWin* window, const char* const command, gchar** args);
 gboolean cmd_editor(ProfWin* window, const char* const command, gchar** args);
 gboolean cmd_correct_editor(ProfWin* window, const char* const command, gchar** args);
@@ -253,5 +254,13 @@ gboolean cmd_silence(ProfWin* window, const char* const command, gchar** args);
 gboolean cmd_register(ProfWin* window, const char* const command, gchar** args);
 gboolean cmd_mood(ProfWin* window, const char* const command, gchar** args);
 gboolean cmd_stamp(ProfWin* window, const char* const command, gchar** args);
+gboolean cmd_vcard(ProfWin* window, const char* const command, gchar** args);
+gboolean cmd_vcard_add(ProfWin* window, const char* const command, gchar** args);
+gboolean cmd_vcard_remove(ProfWin* window, const char* const command, gchar** args);
+gboolean cmd_vcard_get(ProfWin* window, const char* const command, gchar** args);
+gboolean cmd_vcard_photo(ProfWin* window, const char* const command, gchar** args);
+gboolean cmd_vcard_refresh(ProfWin* window, const char* const command, gchar** args);
+gboolean cmd_vcard_set(ProfWin* window, const char* const command, gchar** args);
+gboolean cmd_vcard_save(ProfWin* window, const char* const command, gchar** args);
 
 #endif