about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/command/cmd_ac.c6
-rw-r--r--src/command/cmd_defs.c4
-rw-r--r--src/command/cmd_funcs.c6
-rw-r--r--src/xmpp/avatar.c55
-rw-r--r--src/xmpp/avatar.h1
-rw-r--r--src/xmpp/stanza.c101
-rw-r--r--src/xmpp/stanza.h4
-rw-r--r--tests/unittests/xmpp/stub_avatar.c5
8 files changed, 181 insertions, 1 deletions
diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c
index ec0e9c30..9fc70a1d 100644
--- a/src/command/cmd_ac.c
+++ b/src/command/cmd_ac.c
@@ -1057,6 +1057,7 @@ cmd_ac_init(void)
     autocomplete_add(correction_ac, "char");
 
     avatar_ac = autocomplete_new();
+    autocomplete_add(avatar_ac, "set");
     autocomplete_add(avatar_ac, "get");
     autocomplete_add(avatar_ac, "open");
 
@@ -4101,6 +4102,11 @@ _avatar_autocomplete(ProfWin* window, const char* const input, gboolean previous
 
     jabber_conn_status_t conn_status = connection_get_status();
     if (conn_status == JABBER_CONNECTED) {
+        result = cmd_ac_complete_filepath(input, "/avatar set", previous);
+        if (result) {
+            return result;
+        }
+
         result = autocomplete_param_with_func(input, "/avatar get", roster_barejid_autocomplete, previous, NULL);
         if (result) {
             return result;
diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c
index e62e2d2d..ffc4ec83 100644
--- a/src/command/cmd_defs.c
+++ b/src/command/cmd_defs.c
@@ -2443,16 +2443,20 @@ static struct cmd_t command_defs[] = {
       CMD_TAGS(
               CMD_TAG_CHAT)
       CMD_SYN(
+              "/avatar set <path>",
               "/avatar get <barejid>",
               "/avatar open <barejid>")
       CMD_DESC(
+              "Upload avatar for oneself (XEP-0084). "
               "Download avatar (XEP-0084) for a certain contact. "
               "If nothing happens after using this command the user either doesn't have an avatar set at all "
               "or doesn't use XEP-0084 to publish it.")
       CMD_ARGS(
+              { "set <path>", "Set avatar to the img at <path>." },
               { "get <barejid>", "Download the avatar. barejid is the JID to download avatar from." },
               { "open <barejid>", "Download avatar and open it with command." })
       CMD_EXAMPLES(
+              "/avatar set ~/images/avatar.png",
               "/avatar get thor@valhalla.edda",
               "/avatar open freyja@vanaheimr.edda") },
 
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index 9e821ede..26eb9c3e 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -9195,7 +9195,11 @@ cmd_avatar(ProfWin* window, const char* const command, gchar** args)
         return TRUE;
     }
 
-    if (g_strcmp0(args[0], "get") == 0) {
+    if (g_strcmp0(args[0], "set") == 0) {
+        if (avatar_set(args[1])) {
+            cons_show("Avatar updated successfully");
+        }
+    } else if (g_strcmp0(args[0], "get") == 0) {
         avatar_get_by_nick(args[1], false);
     } else if (g_strcmp0(args[0], "open") == 0) {
         avatar_get_by_nick(args[1], true);
diff --git a/src/xmpp/avatar.c b/src/xmpp/avatar.c
index b962fcef..d826dd90 100644
--- a/src/xmpp/avatar.c
+++ b/src/xmpp/avatar.c
@@ -36,10 +36,12 @@
 #include "config.h"
 
 #include <glib.h>
+#include <gdk-pixbuf-2.0/gdk-pixbuf/gdk-pixbuf.h>
 #include <string.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
+#include <assert.h>
 #include <sys/stat.h>
 
 #include "log.h"
@@ -59,6 +61,7 @@ typedef struct avatar_metadata
 
 static GHashTable* looking_for = NULL; // contains nicks/barejids from who we want to get the avatar
 static GHashTable* shall_open = NULL;  // contains a list of nicks that shall not just downloaded but also opened
+const int MAX_PIXEL = 192;
 
 static void _avatar_request_item_by_id(const char* jid, avatar_metadata* data);
 static int _avatar_metadata_handler(xmpp_stanza_t* const stanza, void* const userdata);
@@ -93,6 +96,58 @@ avatar_pep_subscribe(void)
 }
 
 gboolean
+avatar_set(const char* path)
+{
+    char* expanded_path = get_expanded_path(path);
+
+    GError* err = NULL;
+    GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file(expanded_path, &err);
+
+    if (pixbuf == NULL) {
+        cons_show("An error occurred while opening %s: %s.", expanded_path, err ? err->message : "No error message given");
+        return FALSE;
+    }
+    free(expanded_path);
+
+    // Scale img
+    int w = gdk_pixbuf_get_width(pixbuf);
+    int h = gdk_pixbuf_get_height(pixbuf);
+
+    if (w >= h && w > MAX_PIXEL) {
+        int dest_height = (int)((float)MAX_PIXEL / w * h);
+        GdkPixbuf* new_pixbuf = gdk_pixbuf_scale_simple(pixbuf, MAX_PIXEL, dest_height, GDK_INTERP_BILINEAR);
+        g_object_unref(pixbuf);
+        pixbuf = new_pixbuf;
+    } else if (h > w && w > MAX_PIXEL) {
+        int dest_width = (int)((float)MAX_PIXEL / h * w);
+        GdkPixbuf* new_pixbuf = gdk_pixbuf_scale_simple(pixbuf, dest_width, MAX_PIXEL, GDK_INTERP_BILINEAR);
+        g_object_unref(pixbuf);
+        pixbuf = new_pixbuf;
+    }
+
+    gchar* img_data;
+    gsize len = -1;
+
+    if (!gdk_pixbuf_save_to_buffer(pixbuf, &img_data, &len, "png", &err, NULL)) {
+        cons_show("Unable to scale and convert avatar.");
+        return FALSE;
+    }
+
+    xmpp_ctx_t* const ctx = connection_get_ctx();
+    xmpp_stanza_t* iq = stanza_create_avatar_data_publish_iq(ctx, img_data, len);
+    iq_send_stanza(iq);
+    xmpp_stanza_release(iq);
+
+    iq = stanza_create_avatar_metadata_publish_iq(ctx, img_data, len, gdk_pixbuf_get_height(pixbuf), gdk_pixbuf_get_width(pixbuf));
+    free(img_data);
+    g_object_unref(pixbuf);
+    iq_send_stanza(iq);
+    xmpp_stanza_release(iq);
+
+    return TRUE;
+}
+
+gboolean
 avatar_get_by_nick(const char* nick, gboolean open)
 {
     // in case we set the feature, remove it
diff --git a/src/xmpp/avatar.h b/src/xmpp/avatar.h
index 98532917..1767a2b2 100644
--- a/src/xmpp/avatar.h
+++ b/src/xmpp/avatar.h
@@ -40,5 +40,6 @@
 
 void avatar_pep_subscribe(void);
 gboolean avatar_get_by_nick(const char* nick, gboolean open);
+gboolean avatar_set(const char* path);
 
 #endif
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index bfb782da..8dcad982 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -2587,6 +2587,107 @@ stanza_create_avatar_retrieve_data_request(xmpp_ctx_t* ctx, const char* stanza_i
 }
 
 xmpp_stanza_t*
+stanza_create_avatar_data_publish_iq(xmpp_ctx_t* ctx, const char* img_data, gsize len)
+{
+    char* id = connection_create_stanza_id();
+    xmpp_stanza_t* iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id);
+    free(id);
+    xmpp_stanza_set_attribute(iq, STANZA_ATTR_FROM, connection_get_fulljid());
+
+    xmpp_stanza_t* pubsub = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(pubsub, STANZA_NAME_PUBSUB);
+    xmpp_stanza_set_ns(pubsub, STANZA_NS_PUBSUB);
+
+    xmpp_stanza_t* publish = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(publish, STANZA_NAME_PUBLISH);
+    xmpp_stanza_set_attribute(publish, STANZA_ATTR_NODE, STANZA_NS_USER_AVATAR_DATA);
+
+    xmpp_stanza_t* item = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(item, STANZA_NAME_ITEM);
+    char* sha1 = xmpp_sha1(ctx, (guchar*)img_data, len);
+    xmpp_stanza_set_attribute(item, "id", sha1);
+    xmpp_free(ctx, sha1);
+
+    xmpp_stanza_t* data = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(data, STANZA_NAME_DATA);
+    xmpp_stanza_set_ns(data, STANZA_NS_USER_AVATAR_DATA);
+
+    xmpp_stanza_t* text = xmpp_stanza_new(ctx);
+    gchar* base64 = g_base64_encode((guchar*)img_data, len);
+    xmpp_stanza_set_text(text, base64);
+    free(base64);
+
+    xmpp_stanza_add_child(data, text);
+    xmpp_stanza_add_child(item, data);
+    xmpp_stanza_add_child(publish, item);
+    xmpp_stanza_add_child(pubsub, publish);
+    xmpp_stanza_add_child(iq, pubsub);
+
+    xmpp_stanza_release(text);
+    xmpp_stanza_release(data);
+    xmpp_stanza_release(item);
+    xmpp_stanza_release(publish);
+    xmpp_stanza_release(pubsub);
+
+    return iq;
+}
+
+xmpp_stanza_t*
+stanza_create_avatar_metadata_publish_iq(xmpp_ctx_t* ctx, const char* img_data, gsize len, int height, int width)
+{
+    char* id = id = connection_create_stanza_id();
+    xmpp_stanza_t* iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id);
+    free(id);
+    xmpp_stanza_set_attribute(iq, STANZA_ATTR_FROM, connection_get_fulljid());
+
+    xmpp_stanza_t* pubsub = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(pubsub, STANZA_NAME_PUBSUB);
+    xmpp_stanza_set_ns(pubsub, STANZA_NS_PUBSUB);
+
+    xmpp_stanza_t* publish = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(publish, STANZA_NAME_PUBLISH);
+    xmpp_stanza_set_attribute(publish, STANZA_ATTR_NODE, STANZA_NS_USER_AVATAR_METADATA);
+
+    xmpp_stanza_t* item = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(item, STANZA_NAME_ITEM);
+    char* sha1 = xmpp_sha1(ctx, (guchar*)img_data, len);
+    xmpp_stanza_set_attribute(item, "id", sha1);
+
+    xmpp_stanza_t* metadata = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(metadata, STANZA_NAME_METADATA);
+    xmpp_stanza_set_ns(metadata, STANZA_NS_USER_AVATAR_METADATA);
+
+    xmpp_stanza_t* info = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(info, STANZA_NAME_INFO);
+    xmpp_stanza_set_attribute(info, "id", sha1);
+    xmpp_free(ctx, sha1);
+    char* bytes = g_strdup_printf("%lu", len);
+    char* h = g_strdup_printf("%d", height);
+    char* w = g_strdup_printf("%d", width);
+    xmpp_stanza_set_attribute(info, "bytes", bytes);
+    xmpp_stanza_set_attribute(info, "type", "img/png");
+    xmpp_stanza_set_attribute(info, "height", h);
+    xmpp_stanza_set_attribute(info, "width", w);
+    g_free(bytes);
+    g_free(h);
+    g_free(w);
+
+    xmpp_stanza_add_child(metadata, info);
+    xmpp_stanza_add_child(item, metadata);
+    xmpp_stanza_add_child(publish, item);
+    xmpp_stanza_add_child(pubsub, publish);
+    xmpp_stanza_add_child(iq, pubsub);
+
+    xmpp_stanza_release(info);
+    xmpp_stanza_release(metadata);
+    xmpp_stanza_release(item);
+    xmpp_stanza_release(publish);
+    xmpp_stanza_release(pubsub);
+
+    return iq;
+}
+
+xmpp_stanza_t*
 stanza_attach_correction(xmpp_ctx_t* ctx, xmpp_stanza_t* stanza, const char* const replace_id)
 {
     xmpp_stanza_t* replace_stanza = xmpp_stanza_new(ctx);
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index 6e201497..12c9a5ee 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -62,6 +62,8 @@
 #define STANZA_NAME_PUBLIC_KEYS_LIST "public-keys-list"
 #define STANZA_NAME_PUBKEY_METADATA  "pubkey-metadata"
 #define STANZA_NAME_DATA             "data"
+#define STANZA_NAME_METADATA         "metadata"
+#define STANZA_NAME_INFO             "info"
 #define STANZA_NAME_SHOW             "show"
 #define STANZA_NAME_STATUS           "status"
 #define STANZA_NAME_IQ               "iq"
@@ -409,6 +411,8 @@ XMPPCaps* stanza_parse_caps(xmpp_stanza_t* const stanza);
 void stanza_free_caps(XMPPCaps* caps);
 
 xmpp_stanza_t* stanza_create_avatar_retrieve_data_request(xmpp_ctx_t* ctx, const char* stanza_id, const char* const item_id, const char* const jid);
+xmpp_stanza_t* stanza_create_avatar_data_publish_iq(xmpp_ctx_t* ctx, const char* img_data, gsize len);
+xmpp_stanza_t* stanza_create_avatar_metadata_publish_iq(xmpp_ctx_t* ctx, const char* img_data, gsize len, int height, int width);
 xmpp_stanza_t* stanza_create_mam_iq(xmpp_ctx_t* ctx, const char* const jid, const char* const startdate, const char* const lastid);
 xmpp_stanza_t* stanza_change_password(xmpp_ctx_t* ctx, const char* const user, const char* const password);
 xmpp_stanza_t* stanza_register_new_account(xmpp_ctx_t* ctx, const char* const user, const char* const password);
diff --git a/tests/unittests/xmpp/stub_avatar.c b/tests/unittests/xmpp/stub_avatar.c
index ccd9cf4d..453d1863 100644
--- a/tests/unittests/xmpp/stub_avatar.c
+++ b/tests/unittests/xmpp/stub_avatar.c
@@ -8,3 +8,8 @@ avatar_get_by_nick(const char* nick)
 {
     return TRUE;
 }
+gboolean
+avatar_set(const char* path)
+{
+    return FALSE;
+}