summaryrefslogtreecommitdiff
path: root/examples/weechat.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/weechat.c')
-rw-r--r--examples/weechat.c355
1 files changed, 251 insertions, 104 deletions
diff --git a/examples/weechat.c b/examples/weechat.c
index 0c26f6e..cd89adb 100644
--- a/examples/weechat.c
+++ b/examples/weechat.c
@@ -1,11 +1,36 @@
-/* This is quite messy and should be refactored, but good enough for
- testing. */
+/*
+ This is quite messy and should be refactored, but good enough for
+ testing.
+
+ Building:
+
+ gcc -fPIC -Wall -Wno-pointer-sign -c `pkg-config --cflags --libs weechat libgsasl libxml-2.0 gnutls rexmpp` examples/weechat.c
+ gcc `pkg-config --cflags --libs weechat libgsasl libxml-2.0 gnutls rexmpp` -shared -fPIC -o weechat.so weechat.o
+ mv weechat.so ~/.weechat/plugins/rexmpp.so
+
+ Usage:
+
+ Connect: /xmpp <jid> <password>
+ Open a chat buffer (from the server buffer): q <jid>
+ Join a conference (from the server buffer): j <room>@<server>/<nick>
+
+ TODO:
+
+ - Refine/rethink control/commands.
+ - Add settings (SASL parameters and regular rexmpp configuration).
+ - Add commands for roster management and other functionality.
+ - Refactor the hacky bits of this plugin.
+*/
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
+#include <libxml/tree.h>
#include "weechat-plugin.h"
#include "rexmpp.h"
+#include "rexmpp_roster.h"
+#include "rexmpp_jid.h"
+#include "rexmpp_openpgp.h"
WEECHAT_PLUGIN_NAME("rexmpp");
WEECHAT_PLUGIN_DESCRIPTION("XMPP plugin using librexmpp");
@@ -22,16 +47,17 @@ struct weechat_rexmpp {
struct weechat_rexmpp_muc {
struct weechat_rexmpp *wr;
- char *jid;
+ struct rexmpp_jid jid;
};
struct t_weechat_plugin *weechat_plugin = NULL;
-void my_logger (const struct weechat_rexmpp *wr,
+void my_logger (rexmpp_t *s,
int priority,
const char *fmt,
va_list args)
{
+ struct weechat_rexmpp *wr = (struct weechat_rexmpp *)s;
char *priority_str = "unknown";
switch (priority) {
case LOG_EMERG: priority_str = "emerg"; break;
@@ -50,143 +76,217 @@ void my_logger (const struct weechat_rexmpp *wr,
weechat_printf(wr->server_buffer, "%s\n", buf);
}
-int my_sasl_property_cb (const struct weechat_rexmpp *wr, Gsasl_property prop) {
- rexmpp_t *s = &wr->rexmpp_state;
+int my_sasl_property_cb (rexmpp_t *s, Gsasl_property prop) {
+ struct weechat_rexmpp *wr = (struct weechat_rexmpp *)s;
if (prop == GSASL_PASSWORD) {
gsasl_property_set (s->sasl_session, GSASL_PASSWORD, wr->password);
return GSASL_OK;
}
if (prop == GSASL_AUTHID) {
- char *domainpart = strchr(s->initial_jid, '@');
- if (domainpart != NULL) {
- int localpart_len = domainpart - s->initial_jid;
- char *localpart = malloc(localpart_len + 1);
- localpart[localpart_len] = 0;
- strncpy(localpart, s->initial_jid, localpart_len);
- gsasl_property_set (s->sasl_session, GSASL_AUTHID, localpart);
- free(localpart);
- return GSASL_OK;
- }
+ gsasl_property_set (s->sasl_session, GSASL_AUTHID, s->initial_jid.local);
+ return GSASL_OK;
}
weechat_printf(wr->server_buffer, "unhandled gsasl property: %d\n", prop);
return GSASL_NO_CALLBACK;
}
-int query_input_cb (const struct weechat_rexmpp *wr, void *data,
+int query_input_cb (const void *ptr, void *data,
struct t_gui_buffer *buffer, const char *input_data)
{
+ struct weechat_rexmpp *wr = (void*)ptr;
rexmpp_t *s = &wr->rexmpp_state;
- char *to = weechat_buffer_get_string(buffer, "name");
+ const char *to = weechat_buffer_get_string(buffer, "name");
xmlNodePtr msg = rexmpp_xml_add_id(s, xmlNewNode(NULL, "message"));
xmlNewProp(msg, "to", to);
xmlNewProp(msg, "type", "chat");
xmlNewTextChild(msg, NULL, "body", input_data);
rexmpp_send(s, msg);
- weechat_printf_date_tags(buffer, 0, "self_msg", "%s\t%s\n", s->assigned_jid, input_data);
+ weechat_printf_date_tags(buffer, 0, "self_msg", "%s\t%s\n", ">", input_data);
return WEECHAT_RC_OK;
}
-int query_close_cb (struct weechat_rexmpp *wr, void *data,
+int query_close_cb (const void *ptr, void *data,
struct t_gui_buffer *buffer)
{
+ /* struct weechat_rexmpp *wr = (void*)ptr; */
return WEECHAT_RC_OK;
}
-int muc_input_cb (const struct weechat_rexmpp_muc *wrm, void *data,
+int muc_input_cb (const void *ptr, void *data,
struct t_gui_buffer *buffer, const char *input_data)
{
+ struct weechat_rexmpp_muc *wrm = (void*)ptr;
rexmpp_t *s = &wrm->wr->rexmpp_state;
- char *to = weechat_buffer_get_string(buffer, "name");
xmlNodePtr msg = rexmpp_xml_add_id(s, xmlNewNode(NULL, "message"));
- xmlNewProp(msg, "to", to);
+ xmlNewProp(msg, "to", wrm->jid.bare);
xmlNewProp(msg, "type", "groupchat");
xmlNewTextChild(msg, NULL, "body", input_data);
rexmpp_send(s, msg);
return WEECHAT_RC_OK;
}
-int muc_close_cb (struct weechat_rexmpp_muc *wrm, void *data,
+int muc_close_cb (const void *ptr, void *data,
struct t_gui_buffer *buffer)
{
+ struct weechat_rexmpp_muc *wrm = (void*)ptr;
rexmpp_t *s = &wrm->wr->rexmpp_state;
xmlNodePtr presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence"));
- xmlNewProp(presence, "from", s->assigned_jid);
- xmlNewProp(presence, "to", wrm->jid);
+ xmlNewProp(presence, "from", s->assigned_jid.full);
+ xmlNewProp(presence, "to", wrm->jid.full);
xmlNewProp(presence, "type", "unavailable");
rexmpp_send(s, presence);
free(wrm);
return WEECHAT_RC_OK;
}
-int my_xml_in_cb (struct weechat_rexmpp *wr, xmlNodePtr node) {
+void display_message (struct t_gui_buffer *buf,
+ const char *display_name,
+ xmlNodePtr body)
+{
+ xmlChar *str = xmlNodeGetContent(body);
+ if (str != NULL) {
+ char tags[4096];
+ snprintf(tags, 4096, "nick_%s", display_name);
+ weechat_printf_date_tags(buf, 0, tags, "%s\t%s\n", display_name, str);
+ xmlFree(str);
+ }
+}
+
+int my_xml_in_cb (rexmpp_t *s, xmlNodePtr node) {
+ struct weechat_rexmpp *wr = (struct weechat_rexmpp *)s;
char *xml_buf = rexmpp_xml_serialize(node);
weechat_printf(wr->server_buffer, "recv: %s\n", xml_buf);
/* free(xml_buf); */
if (rexmpp_xml_match(node, "jabber:client", "message")) {
char *from = xmlGetProp(node, "from");
- char *display_name = from;
- int i, resource_removed = 0;
- for (i = 0; i < strlen(from); i++) {
- if (from[i] == '/') {
- from[i] = 0;
- display_name = from + i + 1;
- resource_removed = 1;
- break;
- }
- }
if (from != NULL) {
- struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", from);
+ struct rexmpp_jid from_jid;
+ rexmpp_jid_parse(from, &from_jid);
+ xmlFree(from);
+ char *display_name = from_jid.full;
+ if (from_jid.resource[0]) {
+ display_name = from_jid.resource;
+ }
+ struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", from_jid.bare);
if (buf == NULL) {
- buf = weechat_buffer_new (from,
+ buf = weechat_buffer_new (from_jid.bare,
&query_input_cb, wr, NULL,
&query_close_cb, wr, NULL);
weechat_buffer_set(buf, "nicklist", "1");
}
- if (resource_removed) {
- from[i] = '/'; /* restore */
- }
xmlNodePtr body = rexmpp_xml_find_child(node, "jabber:client", "body");
- if (body != NULL) {
- xmlChar *str = xmlNodeGetContent(body);
- if (str != NULL) {
- char tags[4096];
- snprintf(tags, 4096, "nick_%s", display_name);
- weechat_printf_date_tags(buf, 0, tags, "%s\t%s\n", display_name, str);
- xmlFree(str);
+
+ xmlNodePtr openpgp = rexmpp_xml_find_child(node, "urn:xmpp:openpgp:0", "openpgp");
+ if (openpgp != NULL) {
+ int valid;
+ xmlNodePtr elem = rexmpp_openpgp_decrypt_verify_message(s, node, &valid);
+ if (! valid) {
+ weechat_printf(buf, "An invalid OpenPGP message!");
+ }
+
+ if (elem != NULL) {
+ xmlNodePtr payload =
+ rexmpp_xml_find_child(elem, "urn:xmpp:openpgp:0", "payload");
+ if (payload != NULL) {
+ xmlNodePtr pl_body =
+ rexmpp_xml_find_child(payload, "jabber:client", "body");
+ if (pl_body != NULL) {
+ display_message(buf, display_name, pl_body);
+ body = NULL;
+ }
+ }
+ xmlFreeNode(elem);
}
}
- xmlFree(from);
+
+ if (body != NULL) {
+ display_message(buf, display_name, body);
+ }
}
}
if (rexmpp_xml_match(node, "jabber:client", "presence")) {
char *presence_type = xmlGetProp(node, "type");
- char *jid = xmlGetProp(node, "from");
- int i;
- char *resource = "";
- for (i = 0; i < strlen(jid); i++) {
- if (jid[i] == '/') {
- jid[i] = 0;
- resource = jid + i + 1;
- break;
+ char *from = xmlGetProp(node, "from");
+ struct rexmpp_jid from_jid;
+ rexmpp_jid_parse(from, &from_jid);
+ xmlFree(from);
+
+ xmlNodePtr muc =
+ rexmpp_xml_find_child(node, "http://jabber.org/protocol/muc#user", "x");
+ if (muc != NULL) {
+
+ /* Handle newly joined MUCs */
+ if (presence_type == NULL) {
+ xmlNodePtr status =
+ rexmpp_xml_find_child(muc, "http://jabber.org/protocol/muc#user",
+ "status");
+ if (status != NULL) {
+ char *code = xmlGetProp(status, "code");
+ if (code != NULL) {
+ if (strcmp(code, "110") == 0) {
+ struct weechat_rexmpp_muc *wrm =
+ malloc(sizeof(struct weechat_rexmpp_muc));
+ wrm->wr = wr;
+ rexmpp_jid_parse(from_jid.full, &(wrm->jid));
+ struct t_gui_buffer *buf =
+ weechat_buffer_search("rexmpp", wrm->jid.bare);
+ if (buf == NULL) {
+ buf = weechat_buffer_new (wrm->jid.bare,
+ &muc_input_cb, wrm, NULL,
+ &muc_close_cb, wrm, NULL);
+ weechat_buffer_set(buf, "nicklist", "1");
+ }
+ }
+ free(code);
+ }
+ }
}
- }
- if (rexmpp_xml_find_child(node, "http://jabber.org/protocol/muc#user", "x")) {
+
/* Update MUC nicklist */
- struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", jid);
+ struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", from_jid.bare);
if (buf != NULL) {
if (presence_type != NULL && strcmp(presence_type, "unavailable") == 0) {
struct t_gui_nick *nick =
- weechat_nicklist_search_nick(buf, NULL, resource);
+ weechat_nicklist_search_nick(buf, NULL, from_jid.resource);
if (nick != NULL) {
weechat_nicklist_remove_nick(buf, nick);
}
} else {
- weechat_nicklist_add_nick(buf, NULL, resource,
+ weechat_nicklist_add_nick(buf, NULL, from_jid.resource,
"bar_fg", "", "lightgreen", 1);
}
}
+ } else if (rexmpp_roster_find_item(s, from_jid.bare, NULL) != NULL) {
+ /* A roster item. */
+ struct t_gui_nick *nick = weechat_nicklist_search_nick(wr->server_buffer, NULL, from_jid.bare);
+ if (presence_type == NULL) {
+ /* An "available" presence: just ensure that it's shown as
+ online. */
+ weechat_nicklist_nick_set(wr->server_buffer, nick, "prefix", "+");
+ } else if (strcmp(presence_type, "unavailable") == 0) {
+ /* An "unavailable" presence: set it to "offline" if there's
+ no remaining online resources (i.e., if we can find an
+ online resource for this bare JID other than the one that
+ just went offline). */
+ xmlNodePtr cur;
+ int found = 0;
+ struct rexmpp_jid cur_from_jid;
+ for (cur = s->roster_presence;
+ cur != NULL;
+ cur = xmlNextElementSibling(cur)) {
+ char *cur_from = xmlGetProp(cur, "from");
+ rexmpp_jid_parse(cur_from, &cur_from_jid);
+ xmlFree(cur_from);
+ if (strcmp(cur_from_jid.bare, from_jid.bare) == 0 &&
+ strcmp(cur_from_jid.resource, from_jid.resource) != 0) {
+ found = 1;
+ }
+ }
+ if (! found) {
+ weechat_nicklist_nick_set(wr->server_buffer, nick, "prefix", "");
+ }
+ }
}
- free(jid);
if (presence_type != NULL) {
free(presence_type);
}
@@ -195,17 +295,25 @@ int my_xml_in_cb (struct weechat_rexmpp *wr, xmlNodePtr node) {
return 0;
}
-int my_xml_out_cb (struct weechat_rexmpp *wr, xmlNodePtr node) {
+int my_xml_out_cb (rexmpp_t *s, xmlNodePtr node) {
+ struct weechat_rexmpp *wr = (struct weechat_rexmpp *)s;
char *xml_buf = rexmpp_xml_serialize(node);
weechat_printf(wr->server_buffer, "send: %s\n", xml_buf);
free(xml_buf);
return 0;
}
+void my_console_print_cb (struct weechat_rexmpp *wr, const char *fmt, va_list args) {
+ char str[4096];
+ vsnprintf(str, 4096, fmt, args);
+ weechat_printf(wr->server_buffer, "%s", str);
+}
+
int
-my_input_cb (const struct weechat_rexmpp *wr, void *data,
+my_input_cb (const void *ptr, void *data,
struct t_gui_buffer *buffer, const char *input_data)
{
+ struct weechat_rexmpp *wr = (void*)ptr;
rexmpp_t *s = &wr->rexmpp_state;
if (input_data[0] == '<') {
xmlDocPtr doc = xmlReadMemory(input_data, strlen(input_data), "", "utf-8", 0);
@@ -222,7 +330,7 @@ my_input_cb (const struct weechat_rexmpp *wr, void *data,
weechat_printf(buffer, "Failed to read a document\n");
}
} else if (input_data[0] == 'q' && input_data[1] == ' ') {
- char *jid = input_data + 2;
+ const char *jid = input_data + 2;
struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", jid);
if (buf == NULL) {
buf = weechat_buffer_new (jid,
@@ -230,42 +338,22 @@ my_input_cb (const struct weechat_rexmpp *wr, void *data,
&query_close_cb, wr, NULL);
weechat_buffer_set(buf, "nicklist", "1");
}
- } else if (input_data[0] == 'j' && input_data[1] == ' ') {
- char *jid = input_data + 2;
- xmlNodePtr presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence"));
- xmlNewProp(presence, "from", s->assigned_jid);
- xmlNewProp(presence, "to", jid);
- xmlNodePtr x = xmlNewNode(NULL, "x");
- xmlNewNs(x, "http://jabber.org/protocol/muc", NULL);
- xmlAddChild(presence, x);
- rexmpp_send(s, presence);
- int i;
- struct weechat_rexmpp_muc *wrm = malloc(sizeof(struct weechat_rexmpp_muc));
- wrm->wr = wr;
- wrm->jid = strdup(jid);
- for (i = 0; i < strlen(jid); i++) {
- if (jid[i] == '/') {
- jid[i] = 0;
- break;
- }
- }
- struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", jid);
- if (buf == NULL) {
- buf = weechat_buffer_new (jid,
- &muc_input_cb, wrm, NULL,
- &muc_close_cb, wrm, NULL);
- weechat_buffer_set(buf, "nicklist", "1");
- }
+ } else {
+ rexmpp_console_feed(s, input_data, strlen(input_data));
}
return WEECHAT_RC_OK;
}
-void my_roster_modify_cb (struct weechat_rexmpp *wr, xmlNodePtr item) {
+void my_roster_modify_cb (rexmpp_t *s, xmlNodePtr item) {
+ struct weechat_rexmpp *wr = (struct weechat_rexmpp *)s;
char *subscription = xmlGetProp(item, "subscription");
char *jid = xmlGetProp(item, "jid");
if (subscription != NULL && strcmp(subscription, "remove") == 0) {
/* delete */
- weechat_nicklist_remove_nick(wr->server_buffer, jid);
+ struct t_gui_nick *nick = weechat_nicklist_search_nick(wr->server_buffer, NULL, jid);
+ if (nick != NULL) {
+ weechat_nicklist_remove_nick(wr->server_buffer, nick);
+ }
} else {
/* add or modify */
weechat_nicklist_add_nick(wr->server_buffer, NULL, jid,
@@ -278,11 +366,12 @@ void my_roster_modify_cb (struct weechat_rexmpp *wr, xmlNodePtr item) {
}
int
-my_close_cb (struct weechat_rexmpp *wr, void *data, struct t_gui_buffer *buffer)
+my_close_cb (const void *ptr, void *data, struct t_gui_buffer *buffer)
{
/* todo: close MUC buffers first? or at least mark them somehow, so
that they won't attempt to send "unavailable" presence on
closing. */
+ struct weechat_rexmpp *wr = (void*)ptr;
wr->server_buffer = NULL;
rexmpp_stop(&wr->rexmpp_state);
return WEECHAT_RC_OK;
@@ -290,7 +379,8 @@ my_close_cb (struct weechat_rexmpp *wr, void *data, struct t_gui_buffer *buffer)
void iter (struct weechat_rexmpp *wr, fd_set *rfds, fd_set *wfds);
-int fd_read_cb (const struct weechat_rexmpp *wr, void *data, int fd) {
+int fd_read_cb (const void *ptr, void *data, int fd) {
+ struct weechat_rexmpp *wr = (void*)ptr;
/* weechat_printf(wr->server_buffer, "read hook fired"); */
fd_set read_fds, write_fds;
FD_ZERO(&read_fds);
@@ -300,7 +390,8 @@ int fd_read_cb (const struct weechat_rexmpp *wr, void *data, int fd) {
return 0;
}
-int fd_write_cb (const struct weechat_rexmpp *wr, void *data, int fd) {
+int fd_write_cb (const void *ptr, void *data, int fd) {
+ struct weechat_rexmpp *wr = (void*)ptr;
/* weechat_printf(wr->server_buffer, "write hook fired"); */
fd_set read_fds, write_fds;
FD_ZERO(&read_fds);
@@ -310,7 +401,8 @@ int fd_write_cb (const struct weechat_rexmpp *wr, void *data, int fd) {
return 0;
}
-int timer_cb (const struct weechat_rexmpp *wr, void *data, int remaining_calls) {
+int timer_cb (const void *ptr, void *data, int remaining_calls) {
+ struct weechat_rexmpp *wr = (void*)ptr;
/* weechat_printf(wr->server_buffer, "timer hook fired"); */
fd_set read_fds, write_fds;
FD_ZERO(&read_fds);
@@ -319,8 +411,8 @@ int timer_cb (const struct weechat_rexmpp *wr, void *data, int remaining_calls)
return 0;
}
-void hook_free_cb (void *data, struct t_arraylist *arraylist, struct t_hook *hook) {
- weechat_unhook(hook);
+void hook_free_cb (void *data, struct t_arraylist *arraylist, void *hook) {
+ weechat_unhook((struct t_hook *)hook);
}
void iter (struct weechat_rexmpp *wr, fd_set *rfds, fd_set *wfds) {
@@ -340,7 +432,7 @@ void iter (struct weechat_rexmpp *wr, fd_set *rfds, fd_set *wfds) {
return;
}
if (err != REXMPP_E_AGAIN) {
- weechat_printf(wr->server_buffer, "rexmpp error");
+ weechat_printf(wr->server_buffer, "rexmpp error: %s", rexmpp_strerror(err));
return;
}
fd_set read_fds, write_fds;
@@ -378,6 +470,54 @@ void iter (struct weechat_rexmpp *wr, fd_set *rfds, fd_set *wfds) {
}
int
+command_sc_cb (const void *wr_ptr, void *data,
+ struct t_gui_buffer *buffer,
+ int argc, char **argv, char **argv_eol)
+{
+ struct weechat_rexmpp *wr = (void*)wr_ptr;
+ rexmpp_t *s = &wr->rexmpp_state;
+ const char *to = weechat_buffer_get_string(buffer, "name");
+ xmlNodePtr body = xmlNewNode(NULL, "body");
+ xmlNewNs(body, "jabber:client", NULL);
+ xmlNodeAddContent(body, argv_eol[1]);
+
+ const char *rcpt[2];
+ rcpt[0] = to;
+ rcpt[1] = NULL;
+
+ char *b64 = rexmpp_openpgp_encrypt_sign(s, body, rcpt);
+ if (b64 == NULL) {
+ weechat_printf(buffer, "Failed to encrypt a message.");
+ return WEECHAT_RC_OK;
+ }
+
+ xmlNodePtr openpgp = xmlNewNode(NULL, "openpgp");
+ xmlNewNs(openpgp, "urn:xmpp:openpgp:0", NULL);
+ xmlNodeAddContent(openpgp, b64);
+ free(b64);
+
+ xmlNodePtr msg = rexmpp_xml_add_id(s, xmlNewNode(NULL, "message"));
+ xmlNewProp(msg, "to", to);
+ xmlNewProp(msg, "type", "chat");
+ xmlAddChild(msg, openpgp);
+
+ body = xmlNewNode(NULL, "body");
+ xmlNewNs(body, "jabber:client", NULL);
+ xmlNodeAddContent(body, "This is a secret message.");
+ xmlAddChild(msg, body);
+
+ /* XEP-0380: Explicit Message Encryption */
+ xmlNodePtr eme = xmlNewNode(NULL, "encryption");
+ xmlNewNs(eme, "urn:xmpp:eme:0", NULL);
+ xmlNewProp(eme, "namespace", "urn:xmpp:openpgp:0");
+ xmlAddChild(msg, eme);
+
+ rexmpp_send(s, msg);
+ weechat_printf_date_tags(buffer, 0, "self_msg", "%s\t%s\n", ">", argv_eol[1]);
+ return WEECHAT_RC_OK;
+}
+
+int
command_xmpp_cb (const void *pointer, void *data,
struct t_gui_buffer *buffer,
int argc, char **argv, char **argv_eol)
@@ -391,16 +531,23 @@ command_xmpp_cb (const void *pointer, void *data,
wr->password = strdup(argv[2]);
wr->hooks = weechat_arraylist_new(42, 0, 0, NULL, NULL, hook_free_cb, NULL);
rexmpp_t *s = &wr->rexmpp_state;
- rexmpp_init(s, argv[1]);
- s->log_function = my_logger;
+ rexmpp_init(s, argv[1], my_logger);
s->sasl_property_cb = my_sasl_property_cb;
s->xml_in_cb = my_xml_in_cb;
s->xml_out_cb = my_xml_out_cb;
s->roster_modify_cb = my_roster_modify_cb;
+ s->console_print_cb = my_console_print_cb;
fd_set read_fds, write_fds;
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
iter(wr, &read_fds, &write_fds);
+
+ weechat_hook_command ("sc",
+ "Sign and encrypt a message",
+ "<message>",
+ "message: a message to send",
+ NULL,
+ &command_sc_cb, wr, NULL);
}
return WEECHAT_RC_OK;
}