diff --git a/ChangeLog.adoc b/ChangeLog.adoc index 444398895..e2bd8c6ff 100644 --- a/ChangeLog.adoc +++ b/ChangeLog.adoc @@ -40,7 +40,7 @@ New features:: * api: add optional argument with version in info "version_number" * alias: use lower case for default aliases, rename all aliases to lower case on upgrade (issue #1872) * irc: rename "ssl" options to "tls", connect with TLS and port 6697 by default - * irc: add support of capability "batch" (issue #1292) + * irc: add support of capabilities "batch" and "draft/multiline" (issue #1292, issue #1923) * irc: add command `/rules` (issue #1864) * irc: add command `/knock` (issue #7) * irc: add server option "registered_mode", add fields "authentication_method" and "sasl_mechanism_used" in server (issue #1625) diff --git a/doc/de/weechat_user.de.adoc b/doc/de/weechat_user.de.adoc index de265aa7a..eab65e5cc 100644 --- a/doc/de/weechat_user.de.adoc +++ b/doc/de/weechat_user.de.adoc @@ -3472,6 +3472,7 @@ WeeChat unterstützt folgende https://ircv3.net/irc/[IRCv3 extensions ^↗^,win * <> * <> * <> +* <> * <> * <> * <> @@ -3599,6 +3600,60 @@ Beispiele: -- alice (user@example.com) hat den Host nach test.com geändert .... +// TRANSLATION MISSING +[[irc_ircv3_draft_multiline]] +===== draft/multiline + +Specification: https://ircv3.net/specs/extensions/multiline[multiline ^↗^,window=_blank] + +This capability allows the client and server to send messages with multiple lines, +using the <> capability, that must be enabled as well. + +There are limits in term of bytes or number of lines in a multiline message +that are given in the capability sent by the server, for example: + +.... +CAP alice LS * :draft/multiline=max-bytes=4096,max-lines=24 +.... + +This sets a limit of 4096 bytes and 24 lines for a multiline batched content. + +If the limits are not given by the server, the default in WeeChat are: + +* max bytes: 4096 +* max lines: 24 + +Only standard messages and those send by the <> +command can be multiline. + +ACTION CTCP messages sent with command <> are not affected +by this capability. That means multiline actions are sent as multiple actions. + +[WARNING] +As the specification is a "draft", it may change and the multiline support can +possibly break at any time in WeeChat. + +The capability is automatically enabled if the server supports it and can be +disabled with this command: +`/set irc.server_default.capabilities "*,!draft/multiline"`. + +When the capability is disabled, a multiline message is sent as multiple messages, +as if they were sent sequentially to the server. + +Example of IRC messages sent for a user message with two lines (`this is a test` +/ `on two lines`), send to channel #test: + +.... +BATCH +i8Je7M7gquddoyC9 draft/multiline #test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :this is a test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :on two lines +BATCH -i8Je7M7gquddoyC9 +.... + +Display of the message sent in WeeChat: + +.... +19:01:45 alice | this is a test + | on two lines +.... + [[irc_ircv3_extended_join]] ===== extended-join diff --git a/doc/en/weechat_user.en.adoc b/doc/en/weechat_user.en.adoc index e6b96feaa..825dfabfd 100644 --- a/doc/en/weechat_user.en.adoc +++ b/doc/en/weechat_user.en.adoc @@ -3408,6 +3408,7 @@ WeeChat supports the following https://ircv3.net/irc/[IRCv3 extensions ^↗^,wi * <> * <> * <> +* <> * <> * <> * <> @@ -3535,6 +3536,59 @@ Example: -- alice (user@example.com) has changed host to test.com .... +[[irc_ircv3_draft_multiline]] +===== draft/multiline + +Specification: https://ircv3.net/specs/extensions/multiline[multiline ^↗^,window=_blank] + +This capability allows the client and server to send messages with multiple lines, +using the <> capability, that must be enabled as well. + +There are limits in term of bytes or number of lines in a multiline message +that are given in the capability sent by the server, for example: + +.... +CAP alice LS * :draft/multiline=max-bytes=4096,max-lines=24 +.... + +This sets a limit of 4096 bytes and 24 lines for a multiline batched content. + +If the limits are not given by the server, the default in WeeChat are: + +* max bytes: 4096 +* max lines: 24 + +Only standard messages and those send by the <> +command can be multiline. + +ACTION CTCP messages sent with command <> are not affected +by this capability. That means multiline actions are sent as multiple actions. + +[WARNING] +As the specification is a "draft", it may change and the multiline support can +possibly break at any time in WeeChat. + +The capability is automatically enabled if the server supports it and can be +disabled with this command: +`/set irc.server_default.capabilities "*,!draft/multiline"`. + +When the capability is disabled, a multiline message is sent as multiple messages, +as if they were sent sequentially to the server. + +Example of IRC messages sent for a user message with two lines (`this is a test` +/ `on two lines`), send to channel #test: + +.... +BATCH +i8Je7M7gquddoyC9 draft/multiline #test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :this is a test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :on two lines +BATCH -i8Je7M7gquddoyC9 +.... + +Display of the message sent in WeeChat: + +.... +19:01:45 alice | this is a test + | on two lines +.... + [[irc_ircv3_extended_join]] ===== extended-join diff --git a/doc/fr/weechat_user.fr.adoc b/doc/fr/weechat_user.fr.adoc index f74720696..3dd5254a7 100644 --- a/doc/fr/weechat_user.fr.adoc +++ b/doc/fr/weechat_user.fr.adoc @@ -3512,6 +3512,7 @@ WeeChat supporte les https://ircv3.net/irc/[extensions IRCv3 ^↗^,window=_blan * <> * <> * <> +* <> * <> * <> * <> @@ -3641,6 +3642,65 @@ Exemple : -- alice (user@example.com) a changé d'hôte pour test.com .... +[[irc_ircv3_draft_multiline]] +===== draft/multiline + +Spécification : https://ircv3.net/specs/extensions/multiline[multiline ^↗^,window=_blank] + +Cette capacité autorise le client et le serveur à envoyer des messages avec +plusieurs lignes, en utilisant la capacité <>, qui doit +être activée également. + +Il y a des limites en terme d'octets ou nombre de lignes dans un message +multi-lignes qui sont données dans la capacité envoyée par le serveur, +par exemple : + +.... +CAP alice LS * :draft/multiline=max-bytes=4096,max-lines=24 +.... + +Cela fixe la limite à 4096 octets et 24 lignes pour un contenu "batch" +multi-lignes. + +Si les limites ne sont pas envoyées par le serveur, celles par défaut dans +WeeChat sont : + +* max octets : 4096 +* max lignes : 24 + +Seuls les messages standards et ceux envoyés avec la commande +<> peuvent être multi-lignes. + +Les messages ACTION CTCP envoyés par avec la commande <> +ne sont pas affectés par cette capacité. Cela signifie que les actions +multi-lignes sont envoyées sous forme de plusieurs actions. + +[WARNING] +Comme la spécification est un brouillon (« draft »), elle peut changer et le +support "multiline" peut être cassé à tout moment dans WeeChat. + +La capacité est automatiquement activée si le serveur la supporte et peut être +désactivée avec cette commande : +`/set irc.server_default.capabilities "*,!draft/multiline"`. + +Lorsque la capacité est désactivée, un message multi-lignes est envoyé sous +forme de plusieurs messages, comme s'ils avaient été envoyés séquentiellement +au serveur. + +Exemple de messages IRC envoyés pour un message utilisateur avec deux lignes +(`ceci est un test` / `sur deux lignes`), envoyé au canal #test : + +.... +BATCH +i8Je7M7gquddoyC9 draft/multiline #test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :ceci est un test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :sur deux lignes +BATCH -i8Je7M7gquddoyC9 +.... + +Affichage du message envoyé dans WeeChat : + +.... +19:01:45 alice | ceci est un test + | sur deux lignes +.... + [[irc_ircv3_extended_join]] ===== extended-join diff --git a/doc/it/weechat_user.it.adoc b/doc/it/weechat_user.it.adoc index 2e6b5c0da..fff2d9a0c 100644 --- a/doc/it/weechat_user.it.adoc +++ b/doc/it/weechat_user.it.adoc @@ -3698,6 +3698,7 @@ WeeChat supports the following https://ircv3.net/irc/[IRCv3 extensions ^↗^,wi * <> * <> * <> +* <> * <> * <> * <> @@ -3825,6 +3826,60 @@ Example: -- alice (user@example.com) has changed host to test.com .... +// TRANSLATION MISSING +[[irc_ircv3_draft_multiline]] +===== draft/multiline + +Specification: https://ircv3.net/specs/extensions/multiline[multiline ^↗^,window=_blank] + +This capability allows the client and server to send messages with multiple lines, +using the <> capability, that must be enabled as well. + +There are limits in term of bytes or number of lines in a multiline message +that are given in the capability sent by the server, for example: + +.... +CAP alice LS * :draft/multiline=max-bytes=4096,max-lines=24 +.... + +This sets a limit of 4096 bytes and 24 lines for a multiline batched content. + +If the limits are not given by the server, the default in WeeChat are: + +* max bytes: 4096 +* max lines: 24 + +Only standard messages and those send by the <> +command can be multiline. + +ACTION CTCP messages sent with command <> are not affected +by this capability. That means multiline actions are sent as multiple actions. + +[WARNING] +As the specification is a "draft", it may change and the multiline support can +possibly break at any time in WeeChat. + +The capability is automatically enabled if the server supports it and can be +disabled with this command: +`/set irc.server_default.capabilities "*,!draft/multiline"`. + +When the capability is disabled, a multiline message is sent as multiple messages, +as if they were sent sequentially to the server. + +Example of IRC messages sent for a user message with two lines (`this is a test` +/ `on two lines`), send to channel #test: + +.... +BATCH +i8Je7M7gquddoyC9 draft/multiline #test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :this is a test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :on two lines +BATCH -i8Je7M7gquddoyC9 +.... + +Display of the message sent in WeeChat: + +.... +19:01:45 alice | this is a test + | on two lines +.... + [[irc_ircv3_extended_join]] ===== extended-join diff --git a/doc/ja/weechat_user.ja.adoc b/doc/ja/weechat_user.ja.adoc index d400c2f92..84e7616d2 100644 --- a/doc/ja/weechat_user.ja.adoc +++ b/doc/ja/weechat_user.ja.adoc @@ -3564,6 +3564,7 @@ WeeChat supports the following https://ircv3.net/irc/[IRCv3 extensions ^↗^,wi * <> * <> * <> +* <> * <> * <> * <> @@ -3692,6 +3693,60 @@ Example: -- alice (user@example.com) がホストを test.com に変更しました .... +// TRANSLATION MISSING +[[irc_ircv3_draft_multiline]] +===== draft/multiline + +Specification: https://ircv3.net/specs/extensions/multiline[multiline ^↗^,window=_blank] + +This capability allows the client and server to send messages with multiple lines, +using the <> capability, that must be enabled as well. + +There are limits in term of bytes or number of lines in a multiline message +that are given in the capability sent by the server, for example: + +.... +CAP alice LS * :draft/multiline=max-bytes=4096,max-lines=24 +.... + +This sets a limit of 4096 bytes and 24 lines for a multiline batched content. + +If the limits are not given by the server, the default in WeeChat are: + +* max bytes: 4096 +* max lines: 24 + +Only standard messages and those send by the <> +command can be multiline. + +ACTION CTCP messages sent with command <> are not affected +by this capability. That means multiline actions are sent as multiple actions. + +[WARNING] +As the specification is a "draft", it may change and the multiline support can +possibly break at any time in WeeChat. + +The capability is automatically enabled if the server supports it and can be +disabled with this command: +`/set irc.server_default.capabilities "*,!draft/multiline"`. + +When the capability is disabled, a multiline message is sent as multiple messages, +as if they were sent sequentially to the server. + +Example of IRC messages sent for a user message with two lines (`this is a test` +/ `on two lines`), send to channel #test: + +.... +BATCH +i8Je7M7gquddoyC9 draft/multiline #test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :this is a test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :on two lines +BATCH -i8Je7M7gquddoyC9 +.... + +Display of the message sent in WeeChat: + +.... +19:01:45 alice | this is a test + | on two lines +.... + [[irc_ircv3_extended_join]] ===== extended-join diff --git a/doc/pl/weechat_user.pl.adoc b/doc/pl/weechat_user.pl.adoc index 316decf8a..e5b2e95ab 100644 --- a/doc/pl/weechat_user.pl.adoc +++ b/doc/pl/weechat_user.pl.adoc @@ -3452,6 +3452,7 @@ WeeChat wspiera następujące https://ircv3.net/irc/[rozszerzenia IRCv3 ^↗^,w * <> * <> * <> +* <> * <> * <> * <> @@ -3580,6 +3581,60 @@ Przykłady: -- alice (user@example.com) zmienił hosta na test.com .... +// TRANSLATION MISSING +[[irc_ircv3_draft_multiline]] +===== draft/multiline + +Specification: https://ircv3.net/specs/extensions/multiline[multiline ^↗^,window=_blank] + +This capability allows the client and server to send messages with multiple lines, +using the <> capability, that must be enabled as well. + +There are limits in term of bytes or number of lines in a multiline message +that are given in the capability sent by the server, for example: + +.... +CAP alice LS * :draft/multiline=max-bytes=4096,max-lines=24 +.... + +This sets a limit of 4096 bytes and 24 lines for a multiline batched content. + +If the limits are not given by the server, the default in WeeChat are: + +* max bytes: 4096 +* max lines: 24 + +Only standard messages and those send by the <> +command can be multiline. + +ACTION CTCP messages sent with command <> are not affected +by this capability. That means multiline actions are sent as multiple actions. + +[WARNING] +As the specification is a "draft", it may change and the multiline support can +possibly break at any time in WeeChat. + +The capability is automatically enabled if the server supports it and can be +disabled with this command: +`/set irc.server_default.capabilities "*,!draft/multiline"`. + +When the capability is disabled, a multiline message is sent as multiple messages, +as if they were sent sequentially to the server. + +Example of IRC messages sent for a user message with two lines (`this is a test` +/ `on two lines`), send to channel #test: + +.... +BATCH +i8Je7M7gquddoyC9 draft/multiline #test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :this is a test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :on two lines +BATCH -i8Je7M7gquddoyC9 +.... + +Display of the message sent in WeeChat: + +.... +19:01:45 alice | this is a test + | on two lines +.... + [[irc_ircv3_extended_join]] ===== extended-join diff --git a/doc/sr/weechat_user.sr.adoc b/doc/sr/weechat_user.sr.adoc index bf37be063..547b33ed9 100644 --- a/doc/sr/weechat_user.sr.adoc +++ b/doc/sr/weechat_user.sr.adoc @@ -3202,6 +3202,7 @@ WeeChat подржава следећа https://ircv3.net/irc/[IRCv3 проши * <> * <> * <> +* <> * <> * <> * <> @@ -3328,6 +3329,60 @@ later, when the batch ends. -- alice (user@example.com) је променио свој хост на test.com .... +// TRANSLATION MISSING +[[irc_ircv3_draft_multiline]] +===== draft/multiline + +Specification: https://ircv3.net/specs/extensions/multiline[multiline ^↗^,window=_blank] + +This capability allows the client and server to send messages with multiple lines, +using the <> capability, that must be enabled as well. + +There are limits in term of bytes or number of lines in a multiline message +that are given in the capability sent by the server, for example: + +.... +CAP alice LS * :draft/multiline=max-bytes=4096,max-lines=24 +.... + +This sets a limit of 4096 bytes and 24 lines for a multiline batched content. + +If the limits are not given by the server, the default in WeeChat are: + +* max bytes: 4096 +* max lines: 24 + +Only standard messages and those send by the <> +command can be multiline. + +ACTION CTCP messages sent with command <> are not affected +by this capability. That means multiline actions are sent as multiple actions. + +[WARNING] +As the specification is a "draft", it may change and the multiline support can +possibly break at any time in WeeChat. + +The capability is automatically enabled if the server supports it and can be +disabled with this command: +`/set irc.server_default.capabilities "*,!draft/multiline"`. + +When the capability is disabled, a multiline message is sent as multiple messages, +as if they were sent sequentially to the server. + +Example of IRC messages sent for a user message with two lines (`this is a test` +/ `on two lines`), send to channel #test: + +.... +BATCH +i8Je7M7gquddoyC9 draft/multiline #test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :this is a test +@batch=i8Je7M7gquddoyC9 PRIVMSG #test :on two lines +BATCH -i8Je7M7gquddoyC9 +.... + +Display of the message sent in WeeChat: + +.... +19:01:45 alice | this is a test + | on two lines +.... + [[irc_ircv3_extended_join]] ===== extended-join diff --git a/src/plugins/irc/irc-batch.c b/src/plugins/irc/irc-batch.c index 02f557d3e..87b51b474 100644 --- a/src/plugins/irc/irc-batch.c +++ b/src/plugins/irc/irc-batch.c @@ -30,7 +30,9 @@ #include "irc-batch.h" #include "irc-message.h" #include "irc-protocol.h" +#include "irc-raw.h" #include "irc-server.h" +#include "irc-tag.h" /* @@ -58,6 +60,31 @@ irc_batch_search (struct t_irc_server *server, const char *reference) return NULL; } +/* + * Generates a random batch reference with `size` chars (the next one is the + * final '\0', so the string must be at least size + 1 bytes long). + */ + +void +irc_batch_generate_random_ref (char *string, int size) +{ + const char *chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789"; + + int i, length_chars; + + if (!string || (size < 0)) + return; + + length_chars = strlen (chars); + for (i = 0; i < size; i++) + { + string[i] = chars[rand() % length_chars]; + } + string[size] = '\0'; +} + /* * Adds a batch to list of batched events. */ @@ -199,6 +226,7 @@ irc_batch_process_messages (struct t_irc_server *server, struct t_irc_batch *batch) { char **list_messages, *command, *channel, modifier_data[1024], *new_messages; + char *message; int i, count_messages; if (!batch || !batch->messages) @@ -229,8 +257,12 @@ irc_batch_process_messages (struct t_irc_server *server, { for (i = 0; i < count_messages; i++) { + message = weechat_string_replace (list_messages[i], "\r", "\n"); + if (!message) + continue; + irc_message_parse (server, - list_messages[i], + message, NULL, /* tags */ NULL, /* message_without_tags */ NULL, /* nick */ @@ -246,9 +278,15 @@ irc_batch_process_messages (struct t_irc_server *server, NULL, /* pos_arguments */ NULL, /* pos_channel */ NULL); /* pos_text */ + + /* add raw message */ + irc_raw_print (server, IRC_RAW_FLAG_RECV, message); + /* call receive callback, ignoring batch tags */ - irc_protocol_recv_command (server, list_messages[i], command, - channel, 1); + irc_protocol_recv_command (server, message, command, channel, 1); + + if (message) + free (message); if (command) free (command); if (channel) @@ -317,6 +355,159 @@ irc_batch_end_batch (struct t_irc_server *server, const char *reference) } } +/* + * Processes multiline batch: convert multiple messages into a single one, + * that can include newline chars ("\r" that are converted later to "\n"). + * + * Parameter "target" is the batch target (channel or nick name). + * + * Note: result must be freed after use. + */ + +char * +irc_batch_process_multiline (struct t_irc_server *server, + const char *messages, const char *target) +{ + char **result, **list_messages; + char *tags, *host, *command, *channel, *text; + int i, count_messages; + struct t_hashtable *hash_tags; + + result = weechat_string_dyn_alloc (256); + + list_messages = weechat_string_split (messages, "\n", NULL, 0, 0, + &count_messages); + if (!list_messages) + goto end; + + hash_tags = weechat_hashtable_new (32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, NULL); + + for (i = 0; i < count_messages; i++) + { + irc_message_parse (server, + list_messages[i], + &tags, + NULL, /* message_without_tags */ + NULL, /* nick */ + NULL, /* user */ + &host, + &command, + &channel, + NULL, /* arguments */ + &text, + NULL, /* params */ + NULL, /* num_params */ + NULL, /* pos_command */ + NULL, /* pos_arguments */ + NULL, /* pos_channel */ + NULL); /* pos_text */ + if (host + && command + && ((strcmp (command, "PRIVMSG") == 0) + || (strcmp (command, "NOTICE") == 0)) + && channel + && (strcmp (channel, target) == 0)) + { + if (hash_tags) + { + weechat_hashtable_remove_all (hash_tags); + if (tags && tags[0]) + irc_tag_parse (tags, hash_tags, NULL); + } + if (*result[0]) + { + if (!hash_tags + || !weechat_hashtable_has_key (hash_tags, + "draft/multiline-concat")) + { + weechat_string_dyn_concat (result, "\r", -1); + } + } + else + { + if (tags && tags[0]) + { + weechat_string_dyn_concat (result, "@", -1); + weechat_string_dyn_concat (result, tags, -1); + weechat_string_dyn_concat (result, " ", -1); + } + weechat_string_dyn_concat (result, ":", -1); + weechat_string_dyn_concat (result, host, -1); + weechat_string_dyn_concat (result, " ", -1); + weechat_string_dyn_concat (result, command, -1); + weechat_string_dyn_concat (result, " ", -1); + weechat_string_dyn_concat (result, target, -1); + weechat_string_dyn_concat (result, " :", -1); + } + if (text) + weechat_string_dyn_concat (result, text, -1); + } + if (tags) + free (tags); + if (host) + free (host); + if (command) + free (command); + if (channel) + free (channel); + if (text) + free (text); + } + +end: + if (hash_tags) + weechat_hashtable_free (hash_tags); + if (list_messages) + weechat_string_free_split (list_messages); + + return weechat_string_dyn_free (result, 0); +} + +/* + * Callback for modifier "irc_batch". + */ + +char * +irc_batch_modifier_cb (const void *pointer, void *data, + const char *modifier, const char *modifier_data, + const char *string) +{ + struct t_irc_server *ptr_server; + char **items, *result; + int num_items; + + /* make C compiler happy */ + (void) pointer; + (void) data; + (void) modifier; + + result = NULL; + + if (!modifier_data) + return NULL; + + items = weechat_string_split (modifier_data, ",", NULL, 0, 3, &num_items); + if (!items) + return NULL; + + if (items && (num_items > 1)) + { + ptr_server = irc_server_search (items[0]); + if (ptr_server && (num_items > 2) + && (strcmp (items[1], "draft/multiline") == 0)) + { + result = irc_batch_process_multiline (ptr_server, string, items[2]); + } + } + if (items) + weechat_string_free_split (items); + + return (result) ? result : strdup (string); +} + /* * Returns hdata for batch. */ diff --git a/src/plugins/irc/irc-batch.h b/src/plugins/irc/irc-batch.h index bd8dec30c..04b718dad 100644 --- a/src/plugins/irc/irc-batch.h +++ b/src/plugins/irc/irc-batch.h @@ -39,6 +39,7 @@ struct t_irc_batch struct t_irc_batch *next_batch; /* link to next batch */ }; +extern void irc_batch_generate_random_ref (char *string, int size); extern struct t_irc_batch *irc_batch_search (struct t_irc_server *server, const char *reference); extern struct t_irc_batch *irc_batch_start_batch (struct t_irc_server *server, @@ -54,6 +55,11 @@ extern void irc_batch_end_batch (struct t_irc_server *server, extern void irc_batch_free (struct t_irc_server *server, struct t_irc_batch *batch); extern void irc_batch_free_all (struct t_irc_server *server); +extern char *irc_batch_modifier_cb (const void *pointer, void *data, + const char *modifier, + const char *modifier_data, + const char *string); + extern struct t_hdata *irc_batch_hdata_batch_cb (const void *pointer, void *data, const char *hdata_name); diff --git a/src/plugins/irc/irc-channel.c b/src/plugins/irc/irc-channel.c index fe5342973..d36e75ddb 100644 --- a/src/plugins/irc/irc-channel.c +++ b/src/plugins/irc/irc-channel.c @@ -330,6 +330,7 @@ irc_channel_create_buffer (struct t_irc_server *server, if (buffer_created) { + weechat_buffer_set (ptr_buffer, "input_multiline", "1"); if (!weechat_buffer_get_integer (ptr_buffer, "short_name_is_set")) weechat_buffer_set (ptr_buffer, "short_name", channel_name); } diff --git a/src/plugins/irc/irc-command.c b/src/plugins/irc/irc-command.c index 8cc4e1c97..f176f0dd5 100644 --- a/src/plugins/irc/irc-command.c +++ b/src/plugins/irc/irc-command.c @@ -1105,27 +1105,38 @@ irc_command_me_channel (struct t_irc_server *server, const char *arguments) { struct t_arraylist *list_messages; - int i, list_size; + char **list_arguments; + int i, j, list_size, count_arguments; - list_messages = irc_server_sendf ( - server, - IRC_SERVER_SEND_OUTQ_PRIO_HIGH | IRC_SERVER_SEND_RETURN_LIST, - NULL, - "PRIVMSG %s :\01ACTION %s\01", - channel->name, - (arguments && arguments[0]) ? arguments : ""); - if (list_messages) + list_arguments = weechat_string_split ((arguments) ? arguments : "", + "\n", NULL, 0, 0, &count_arguments); + if (!list_arguments) + return; + + for (i = 0; i < count_arguments; i++) { - list_size = weechat_arraylist_size (list_messages); - for (i = 0; i < list_size; i++) + list_messages = irc_server_sendf ( + server, + IRC_SERVER_SEND_OUTQ_PRIO_HIGH | IRC_SERVER_SEND_RETURN_LIST, + NULL, + "PRIVMSG %s :\01ACTION %s\01", + channel->name, + list_arguments[i]); + if (list_messages) { - irc_command_me_channel_display ( - server, - channel, - (const char *)weechat_arraylist_get (list_messages, i)); + list_size = weechat_arraylist_size (list_messages); + for (j = 0; j < list_size; j++) + { + irc_command_me_channel_display ( + server, + channel, + (const char *)weechat_arraylist_get (list_messages, j)); + } + weechat_arraylist_free (list_messages); } - weechat_arraylist_free (list_messages); } + + weechat_string_free_split (list_arguments); } /* @@ -6950,8 +6961,8 @@ irc_command_init () "\n" "Capabilities supported by WeeChat are: " "account-notify, away-notify, batch, cap-notify, chghost, " - "extended-join, invite-notify, message-tags, multi-prefix, " - "server-time, setname, userhost-in-names.\n" + "draft/multiline, extended-join, invite-notify, message-tags, " + "multi-prefix, server-time, setname, userhost-in-names.\n" "\n" "The capabilities to automatically enable on servers can be set " "in option irc.server_default.capabilities (or by server in " diff --git a/src/plugins/irc/irc-command.h b/src/plugins/irc/irc-command.h index 7a57dfbb8..1de161d98 100644 --- a/src/plugins/irc/irc-command.h +++ b/src/plugins/irc/irc-command.h @@ -59,8 +59,8 @@ struct t_irc_channel; */ #define IRC_COMMAND_CAP_SUPPORTED \ "account-notify|away-notify|batch|cap-notify|chghost|" \ - "extended-join|invite-notify|message-tags|multi-prefix|" \ - "server-time|setname|userhost-in-names" + "draft/multiline|extended-join|invite-notify|message-tags|" \ + "multi-prefix|server-time|setname|userhost-in-names" /* list of supported CTCPs (for completion in command /ctcp) */ #define IRC_COMMAND_CTCP_SUPPORTED_COMPLETION \ diff --git a/src/plugins/irc/irc-message.c b/src/plugins/irc/irc-message.c index e5b2360c1..bc5023727 100644 --- a/src/plugins/irc/irc-message.c +++ b/src/plugins/irc/irc-message.c @@ -26,6 +26,8 @@ #include "../weechat-plugin.h" #include "irc.h" +#include "irc-message.h" +#include "irc-batch.h" #include "irc-channel.h" #include "irc-color.h" #include "irc-config.h" @@ -529,6 +531,112 @@ irc_message_parse_to_hashtable (struct t_irc_server *server, return hashtable; } +/* + * Parses capability value. + * + * For example for this capability: + * draft/multiline=max-bytes=4096,max-lines=24 + * + * The input value must be: "max-bytes=4096,max-lines=24" + * The output is a hashtable with following keys/values (as strings): + * + * "max-bytes": "4096" + * "max-lines": "24" + * + * Note: hashtable must be freed after use. + */ + +struct t_hashtable * +irc_message_parse_cap_value (const char *value) +{ + struct t_hashtable *hashtable; + char **items, *key; + const char *pos; + int i, count_items; + + if (!value) + return NULL; + + hashtable = weechat_hashtable_new (32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, NULL); + if (!hashtable) + return NULL; + + items = weechat_string_split (value, ",", NULL, 0, 0, &count_items); + if (items) + { + for (i = 0; i < count_items; i++) + { + pos = strchr (items[i], '='); + if (pos) + { + key = weechat_strndup (items[i], pos - items[i]); + if (key) + { + weechat_hashtable_set (hashtable, key, pos + 1); + free (key); + } + } + else + { + weechat_hashtable_set (hashtable, items[i], NULL); + } + } + weechat_string_free_split (items); + } + + return hashtable; +} + +/* + * Parses "draft/multiline" cap value and extract: + * - max-bytes: maximum allowed total byte length of multiline batched content + * - max-lines: maximum allowed number of lines in a multiline batch content + */ + +void +irc_message_parse_cap_multiline_value (struct t_irc_server *server, + const char *value) +{ + struct t_hashtable *values; + const char *ptr_value; + char *error; + long number; + + if (!server) + return; + + server->multiline_max_bytes = IRC_SERVER_MULTILINE_DEFAULT_MAX_BYTES; + server->multiline_max_lines = IRC_SERVER_MULTILINE_DEFAULT_MAX_LINES; + + if (!value) + return; + + values = irc_message_parse_cap_value (value); + if (!values) + return; + + ptr_value = (const char *)weechat_hashtable_get (values, "max-bytes"); + if (ptr_value) + { + number = strtol (ptr_value, &error, 10); + if (error && !error[0]) + server->multiline_max_bytes = number; + } + + ptr_value = (const char *)weechat_hashtable_get (values, "max-lines"); + if (ptr_value) + { + number = strtol (ptr_value, &error, 10); + if (error && !error[0]) + server->multiline_max_lines = number; + } + + weechat_hashtable_free (values); +} + /* * Encodes/decodes an IRC message using a charset. * @@ -762,24 +870,27 @@ irc_message_replace_vars (struct t_irc_server *server, */ void -irc_message_split_add (struct t_hashtable *hashtable, int number, +irc_message_split_add (struct t_irc_message_split_context *context, const char *tags, const char *message, const char *arguments) { char key[32], value[32], *buf; int length; + if (!context) + return; + if (message) { length = ((tags) ? strlen (tags) : 0) + strlen (message) + 1; buf = malloc (length); if (buf) { - snprintf (key, sizeof (key), "msg%d", number); + snprintf (key, sizeof (key), "msg%d", context->number); snprintf (buf, length, "%s%s", (tags) ? tags : "", message); - weechat_hashtable_set (hashtable, key, buf); + weechat_hashtable_set (context->hashtable, key, buf); if (weechat_irc_plugin->debug >= 2) { weechat_printf (NULL, @@ -787,12 +898,13 @@ irc_message_split_add (struct t_hashtable *hashtable, int number, key, buf, length - 1); } free (buf); + context->total_bytes += length; } } if (arguments) { - snprintf (key, sizeof (key), "args%d", number); - weechat_hashtable_set (hashtable, key, arguments); + snprintf (key, sizeof (key), "args%d", context->number); + weechat_hashtable_set (context->hashtable, key, arguments); if (weechat_irc_plugin->debug >= 2) { weechat_printf (NULL, @@ -800,8 +912,8 @@ irc_message_split_add (struct t_hashtable *hashtable, int number, key, arguments); } } - snprintf (value, sizeof (value), "%d", number); - weechat_hashtable_set (hashtable, "count", value); + snprintf (value, sizeof (value), "%d", context->number); + weechat_hashtable_set (context->hashtable, "count", value); } /* @@ -837,7 +949,7 @@ irc_message_split_add (struct t_hashtable *hashtable, int number, */ int -irc_message_split_string (struct t_hashtable *hashtable, +irc_message_split_string (struct t_irc_message_split_context *context, const char *tags, const char *host, const char *command, @@ -851,7 +963,9 @@ irc_message_split_string (struct t_hashtable *hashtable, { const char *pos, *pos_max, *pos_next, *pos_last_delim; char message[8192], *dup_arguments; - int number; + + if (!context) + return 0; max_length -= 2; /* by default: 512 - 2 = 510 bytes */ if (max_length_nick_user_host >= 0) @@ -880,8 +994,6 @@ irc_message_split_string (struct t_hashtable *hashtable, max_length); } - number = 1; - if (!arguments || !arguments[0]) { snprintf (message, sizeof (message), "%s%s%s %s%s%s%s", @@ -892,7 +1004,8 @@ irc_message_split_string (struct t_hashtable *hashtable, (target && target[0]) ? " " : "", (prefix) ? prefix : "", (suffix) ? suffix : ""); - irc_message_split_add (hashtable, 1, tags, message, ""); + irc_message_split_add (context, tags, message, ""); + (context->number)++; return 1; } @@ -924,9 +1037,8 @@ irc_message_split_string (struct t_hashtable *hashtable, (prefix) ? prefix : "", dup_arguments, (suffix) ? suffix : ""); - irc_message_split_add (hashtable, number, tags, message, - dup_arguments); - number++; + irc_message_split_add (context, tags, message, dup_arguments); + (context->number)++; free (dup_arguments); } arguments = (pos == pos_last_delim) ? pos + 1 : pos; @@ -945,16 +1057,14 @@ irc_message_split_string (struct t_hashtable *hashtable, */ int -irc_message_split_authenticate (struct t_hashtable *hashtable, +irc_message_split_authenticate (struct t_irc_message_split_context *context, const char *tags, const char *host, const char *command, const char *arguments) { - int number, length; + int length; char message[8192], *args; const char *ptr_args; - number = 1; - length = 0; ptr_args = arguments; while (ptr_args && ptr_args[0]) @@ -972,9 +1082,9 @@ irc_message_split_authenticate (struct t_hashtable *hashtable, (host) ? " " : "", command, args); - irc_message_split_add (hashtable, number, tags, message, args); + irc_message_split_add (context, tags, message, args); free (args); - number++; + (context->number)++; ptr_args += length; } @@ -984,8 +1094,8 @@ irc_message_split_authenticate (struct t_hashtable *hashtable, (host) ? host : "", (host) ? " " : "", command); - irc_message_split_add (hashtable, number, tags, message, "+"); - number++; + irc_message_split_add (context, tags, message, "+"); + (context->number)++; } return 1; @@ -1001,20 +1111,18 @@ irc_message_split_authenticate (struct t_hashtable *hashtable, */ int -irc_message_split_join (struct t_hashtable *hashtable, +irc_message_split_join (struct t_irc_message_split_context *context, const char *tags, const char *host, const char *arguments, int max_length) { - int number, channels_count, keys_count, length, length_no_channel; + int channels_count, keys_count, length, length_no_channel; int length_to_add, index_channel; char **channels, **keys, *pos, *str; char msg_to_send[16384], keys_to_add[16384]; max_length -= 2; /* by default: 512 - 2 = 510 bytes */ - number = 1; - channels = NULL; channels_count = 0; keys = NULL; @@ -1087,11 +1195,11 @@ irc_message_split_join (struct t_hashtable *hashtable, else { strcat (msg_to_send, keys_to_add); - irc_message_split_add (hashtable, number, + irc_message_split_add (context, tags, msg_to_send, msg_to_send + length_no_channel + 1); - number++; + (context->number)++; snprintf (msg_to_send, sizeof (msg_to_send), "%s%sJOIN", (host) ? host : "", (host) ? " " : ""); @@ -1103,7 +1211,7 @@ irc_message_split_join (struct t_hashtable *hashtable, if (length > length_no_channel) { strcat (msg_to_send, keys_to_add); - irc_message_split_add (hashtable, number, + irc_message_split_add (context, tags, msg_to_send, msg_to_send + length_no_channel + 1); @@ -1117,26 +1225,79 @@ irc_message_split_join (struct t_hashtable *hashtable, return 1; } +/* + * Starts batch for multiline message. + */ + +void +irc_message_start_batch (struct t_irc_message_split_context *context, + const char *target, const char *batch_ref) +{ + char msg_batch[4096], args_batch[4096]; + + snprintf (msg_batch, sizeof (msg_batch), + "BATCH +%s draft/multiline %s", + batch_ref, + target); + snprintf (args_batch, sizeof (args_batch), + "+%s draft/multiline %s", + batch_ref, + target); + irc_message_split_add (context, NULL, msg_batch, args_batch); + (context->number)++; +} + +/* + * Ends batch for multiline message. + */ + +void +irc_message_end_batch (struct t_irc_message_split_context *context, + const char *batch_ref) +{ + char msg_batch[4096], args_batch[4096]; + + snprintf (msg_batch, sizeof (msg_batch), + "BATCH -%s", + batch_ref); + snprintf (args_batch, sizeof (args_batch), + "-%s", + batch_ref); + irc_message_split_add (context, NULL, msg_batch, args_batch); + (context->number)++; +} + /* * Splits a PRIVMSG or NOTICE message, taking care of keeping the '\01' char * used in CTCP messages. * + * If multiline == 1, the message is split on newline chars ('\n') and is sent + * using BATCH command (to group messages together). + * * Returns: * 1: OK * 0: error */ int -irc_message_split_privmsg_notice (struct t_hashtable *hashtable, - const char *tags, const char *host, - const char *command, const char *target, +irc_message_split_privmsg_notice (struct t_irc_message_split_context *context, + const char *tags, + const char *host, + const char *command, + const char *target, const char *arguments, int max_length_nick_user_host, - int max_length) + int max_length, + int multiline, + int multiline_max_bytes, + int multiline_max_lines) { - char *arguments2, prefix[4096], suffix[2], *pos, saved_char; + char prefix[4096], suffix[2], *pos, saved_char, name[256]; + char tags_multiline[4096], **list_lines, batch_ref[16 + 1]; + char **multiline_args; const char *ptr_args; - int length, rc; + int i, length, length_tags, rc, count_lines, batch_lines; + int index_multiline_args; /* * message sent looks like: @@ -1146,41 +1307,122 @@ irc_message_split_privmsg_notice (struct t_hashtable *hashtable, * :nick!user@host.com PRIVMSG #channel :hello world! */ - arguments2 = strdup (arguments); - if (!arguments2) - return 0; + rc = 1; - ptr_args = arguments2; - - /* for CTCP, prefix will be ":\01xxxx " and suffix "\01" */ - prefix[0] = '\0'; - suffix[0] = '\0'; - length = strlen (arguments2); - if ((arguments2[0] == '\01') - && (arguments2[length - 1] == '\01')) + if (multiline) { - pos = strchr (arguments2, ' '); - if (pos) + index_multiline_args = 1; + multiline_args = weechat_string_dyn_alloc (256); + if (!multiline_args) + return 0; + + irc_batch_generate_random_ref (batch_ref, sizeof (batch_ref) - 1); + + /* start batch */ + irc_message_start_batch (context, target, batch_ref); + + /* add messages */ + list_lines = weechat_string_split (arguments, "\n", NULL, 0, 0, + &count_lines); + if (list_lines) { - pos++; - saved_char = pos[0]; - pos[0] = '\0'; - snprintf (prefix, sizeof (prefix), ":%s", arguments2); - pos[0] = saved_char; - arguments2[length - 1] = '\0'; - ptr_args = pos; - suffix[0] = '\01'; - suffix[1] = '\0'; + batch_lines = 0; + for (i = 0; i < count_lines; i++) + { + if (tags && tags[0]) + { + snprintf (tags_multiline, sizeof (tags_multiline), + "@batch=%s;%s", + batch_ref, + tags + 1); + } + else + { + snprintf (tags_multiline, sizeof (tags_multiline), + "@batch=%s ", + batch_ref); + } + length_tags = strlen (tags_multiline); + rc &= irc_message_split_string ( + context, tags_multiline, host, command, target, ":", + list_lines[i], "", ' ', max_length_nick_user_host, + max_length); + if (batch_lines > 0) + weechat_string_dyn_concat (multiline_args, "\n", -1); + weechat_string_dyn_concat (multiline_args, + list_lines[i], -1); + batch_lines++; + if ((i < count_lines - 1) + && ((batch_lines >= multiline_max_lines) + || (context->total_bytes + length_tags + + (int)strlen (list_lines[i + 1]) >= multiline_max_bytes))) + { + /* start new batch if we have reached max lines/bytes */ + irc_message_end_batch (context, batch_ref); + snprintf (name, sizeof (name), + "multiline_args%d", index_multiline_args); + weechat_hashtable_set (context->hashtable, name, + *multiline_args); + weechat_string_dyn_copy (multiline_args, NULL); + index_multiline_args++; + irc_batch_generate_random_ref (batch_ref, + sizeof (batch_ref) - 1); + context->total_bytes = 0; + irc_message_start_batch (context, target, batch_ref); + batch_lines = 0; + } + } + weechat_string_free_split (list_lines); + } + + /* end batch */ + irc_message_end_batch (context, batch_ref); + + snprintf (name, sizeof (name), + "multiline_args%d", index_multiline_args); + weechat_hashtable_set (context->hashtable, name, *multiline_args); + weechat_string_dyn_free (multiline_args, 1); + } + else + { + list_lines = weechat_string_split (arguments, "\n", NULL, 0, 0, + &count_lines); + if (list_lines) + { + for (i = 0; i < count_lines; i++) + { + /* for CTCP, prefix is ":\01xxxx " and suffix "\01" */ + prefix[0] = '\0'; + suffix[0] = '\0'; + ptr_args = list_lines[i]; + length = strlen (list_lines[i]); + if ((list_lines[i][0] == '\01') + && (list_lines[i][length - 1] == '\01')) + { + pos = strchr (list_lines[i], ' '); + if (pos) + { + pos++; + saved_char = pos[0]; + pos[0] = '\0'; + snprintf (prefix, sizeof (prefix), ":%s", list_lines[i]); + pos[0] = saved_char; + list_lines[i][length - 1] = '\0'; + ptr_args = pos; + suffix[0] = '\01'; + suffix[1] = '\0'; + } + } + if (!prefix[0]) + strcpy (prefix, ":"); + rc = irc_message_split_string (context, tags, host, command, target, + prefix, ptr_args, suffix, + ' ', max_length_nick_user_host, + max_length); + } + weechat_string_free_split (list_lines); } } - if (!prefix[0]) - strcpy (prefix, ":"); - - rc = irc_message_split_string (hashtable, tags, host, command, target, - prefix, ptr_args, suffix, - ' ', max_length_nick_user_host, max_length); - - free (arguments2); return rc; } @@ -1194,7 +1436,7 @@ irc_message_split_privmsg_notice (struct t_hashtable *hashtable, */ int -irc_message_split_005 (struct t_hashtable *hashtable, +irc_message_split_005 (struct t_irc_message_split_context *context, const char *tags, const char *host, const char *command, const char *target, const char *arguments, int max_length) @@ -1218,7 +1460,7 @@ irc_message_split_005 (struct t_hashtable *hashtable, pos[0] = '\0'; } - return irc_message_split_string (hashtable, tags, host, command, target, + return irc_message_split_string (context, tags, host, command, target, NULL, arguments, suffix, ' ', -1, max_length); } @@ -1254,11 +1496,16 @@ irc_message_split_005 (struct t_hashtable *hashtable, struct t_hashtable * irc_message_split (struct t_irc_server *server, const char *message) { - struct t_hashtable *hashtable; + struct t_irc_message_split_context split_context; char **argv, **argv_eol, *tags, *host, *command, *arguments, target[4096]; char *pos, monitor_action[3]; int split_ok, argc, index_args, max_length_nick, max_length_user; int max_length_host, max_length_nick_user_host, split_msg_max_length; + int multiline, multiline_max_bytes, multiline_max_lines; + + split_context.hashtable = NULL; + split_context.number = 1; + split_context.total_bytes = 0; split_ok = 0; tags = NULL; @@ -1267,22 +1514,23 @@ irc_message_split (struct t_irc_server *server, const char *message) arguments = NULL; argv = NULL; argv_eol = NULL; + multiline = 0; if (server) { split_msg_max_length = IRC_SERVER_OPTION_INTEGER( server, IRC_SERVER_OPTION_SPLIT_MSG_MAX_LENGTH); - - /* - * split disabled? use a very high max_length so the message should - * never be split - */ + /* if split disabled, use a high max_length to prevent any split */ if (split_msg_max_length == 0) split_msg_max_length = INT_MAX - 16; + multiline_max_bytes = server->multiline_max_bytes; + multiline_max_lines = server->multiline_max_lines; } else { - split_msg_max_length = 512; /* max length by default */ + split_msg_max_length = 512; + multiline_max_bytes = 0; + multiline_max_lines = 0; } /* debug message */ @@ -1292,11 +1540,11 @@ irc_message_split (struct t_irc_server *server, const char *message) message, split_msg_max_length); } - hashtable = weechat_hashtable_new (32, - WEECHAT_HASHTABLE_STRING, - WEECHAT_HASHTABLE_STRING, - NULL, NULL); - if (!hashtable) + split_context.hashtable = weechat_hashtable_new (32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, NULL); + if (!split_context.hashtable) return NULL; if (!message || !message[0]) @@ -1342,6 +1590,17 @@ irc_message_split (struct t_irc_server *server, const char *message) index_args = 1; } + multiline = ( + ((weechat_strcasecmp (command, "privmsg") == 0) + || (weechat_strcasecmp (command, "notice") == 0)) + && message + && strchr (message, '\n') + && (index_args + 1 <= argc - 1) + && (weechat_strncmp (argv[index_args + 1], "\01", 1) != 0) + && (weechat_strncmp (argv[index_args + 1], ":\01", 2) != 0) + && weechat_hashtable_has_key (server->cap_list, "batch") + && weechat_hashtable_has_key (server->cap_list, "draft/multiline")); + max_length_nick = (server && (server->nick_max_length > 0)) ? server->nick_max_length : 16; max_length_user = (server && (server->user_max_length > 0)) ? @@ -1361,7 +1620,7 @@ irc_message_split (struct t_irc_server *server, const char *message) { /* AUTHENTICATE UzXAmVffxuzFy77XWBGwABBQAgdinelBrKZaR3wE7nsIETuTVY= */ split_ok = irc_message_split_authenticate ( - hashtable, tags, host, command, arguments); + &split_context, tags, host, command, arguments); } else if ((weechat_strcasecmp (command, "ison") == 0) || (weechat_strcasecmp (command, "wallops") == 0)) @@ -1371,7 +1630,7 @@ irc_message_split (struct t_irc_server *server, const char *message) * WALLOPS :some text here */ split_ok = irc_message_split_string ( - hashtable, tags, host, command, NULL, ":", + &split_context, tags, host, command, NULL, ":", (argv_eol[index_args][0] == ':') ? argv_eol[index_args] + 1 : argv_eol[index_args], NULL, ' ', max_length_nick_user_host, split_msg_max_length); @@ -1388,14 +1647,14 @@ irc_message_split (struct t_irc_server *server, const char *message) snprintf (monitor_action, sizeof (monitor_action), "%c ", argv_eol[index_args][0]); split_ok = irc_message_split_string ( - hashtable, tags, host, command, NULL, monitor_action, + &split_context, tags, host, command, NULL, monitor_action, argv_eol[index_args] + 2, NULL, ',', max_length_nick_user_host, split_msg_max_length); } else { split_ok = irc_message_split_string ( - hashtable, tags, host, command, NULL, ":", + &split_context, tags, host, command, NULL, ":", (argv_eol[index_args][0] == ':') ? argv_eol[index_args] + 1 : argv_eol[index_args], NULL, ',', max_length_nick_user_host, split_msg_max_length); @@ -1407,7 +1666,7 @@ irc_message_split (struct t_irc_server *server, const char *message) if ((int)strlen (message) > split_msg_max_length - 2) { /* split join if it's too long */ - split_ok = irc_message_split_join (hashtable, tags, host, + split_ok = irc_message_split_join (&split_context, tags, host, arguments, split_msg_max_length); } } @@ -1421,10 +1680,11 @@ irc_message_split (struct t_irc_server *server, const char *message) if (index_args + 1 <= argc - 1) { split_ok = irc_message_split_privmsg_notice ( - hashtable, tags, host, command, argv[index_args], + &split_context, tags, host, command, argv[index_args], (argv_eol[index_args + 1][0] == ':') ? argv_eol[index_args + 1] + 1 : argv_eol[index_args + 1], - max_length_nick_user_host, split_msg_max_length); + max_length_nick_user_host, split_msg_max_length, + multiline, multiline_max_bytes, multiline_max_lines); } } else if (weechat_strcasecmp (command, "005") == 0) @@ -1433,7 +1693,7 @@ irc_message_split (struct t_irc_server *server, const char *message) if (index_args + 1 <= argc - 1) { split_ok = irc_message_split_005 ( - hashtable, tags, host, command, argv[index_args], + &split_context, tags, host, command, argv[index_args], (argv_eol[index_args + 1][0] == ':') ? argv_eol[index_args + 1] + 1 : argv_eol[index_args + 1], split_msg_max_length); @@ -1452,7 +1712,7 @@ irc_message_split (struct t_irc_server *server, const char *message) snprintf (target, sizeof (target), "%s %s", argv[index_args], argv[index_args + 1]); split_ok = irc_message_split_string ( - hashtable, tags, host, command, target, ":", + &split_context, tags, host, command, target, ":", (argv_eol[index_args + 2][0] == ':') ? argv_eol[index_args + 2] + 1 : argv_eol[index_args + 2], NULL, ' ', -1, split_msg_max_length); @@ -1465,7 +1725,7 @@ irc_message_split (struct t_irc_server *server, const char *message) argv[index_args], argv[index_args + 1], argv[index_args + 2]); split_ok = irc_message_split_string ( - hashtable, tags, host, command, target, ":", + &split_context, tags, host, command, target, ":", (argv_eol[index_args + 3][0] == ':') ? argv_eol[index_args + 3] + 1 : argv_eol[index_args + 3], NULL, ' ', -1, split_msg_max_length); @@ -1476,10 +1736,11 @@ irc_message_split (struct t_irc_server *server, const char *message) end: if (!split_ok - || (weechat_hashtable_get_integer (hashtable, "items_count") == 0)) + || (weechat_hashtable_get_integer (split_context.hashtable, + "items_count") == 0)) { - irc_message_split_add (hashtable, - (message) ? 1 : 0, + split_context.number = (message) ? 1 : 0; + irc_message_split_add (&split_context, tags, message, arguments); @@ -1492,5 +1753,5 @@ end: if (argv_eol) weechat_string_free_split (argv_eol); - return hashtable; + return split_context.hashtable; } diff --git a/src/plugins/irc/irc-message.h b/src/plugins/irc/irc-message.h index 496c7c1f9..6583ff44a 100644 --- a/src/plugins/irc/irc-message.h +++ b/src/plugins/irc/irc-message.h @@ -20,6 +20,14 @@ #ifndef WEECHAT_PLUGIN_IRC_MESSAGE_H #define WEECHAT_PLUGIN_IRC_MESSAGE_H +struct t_irc_message_split_context +{ + struct t_hashtable *hashtable; /* hashtable with msgs/args/count */ + int number; /* current msg index (starts to 1) */ + long total_bytes; /* total bytes of messages split */ + /* (+ 1 byte between each message) */ +}; + struct t_irc_server; struct t_irc_channel; @@ -35,6 +43,9 @@ extern void irc_message_parse (struct t_irc_server *server, const char *message, int *pos_channel, int *pos_text); extern struct t_hashtable *irc_message_parse_to_hashtable (struct t_irc_server *server, const char *message); +extern struct t_hashtable *irc_message_parse_cap_value (const char *value); +extern void irc_message_parse_cap_multiline_value (struct t_irc_server *server, + const char *value); extern char *irc_message_convert_charset (const char *message, int pos_start, const char *modifier, diff --git a/src/plugins/irc/irc-protocol.c b/src/plugins/irc/irc-protocol.c index 3ba8a45c1..d5c59b909 100644 --- a/src/plugins/irc/irc-protocol.c +++ b/src/plugins/irc/irc-protocol.c @@ -966,6 +966,11 @@ IRC_PROTOCOL_CALLBACK(cap) { weechat_hashtable_set (server->cap_ls, str_name, pos_value + 1); + if (strcmp (str_name, "draft/multiline") == 0) + { + irc_message_parse_cap_multiline_value ( + server, pos_value + 1); + } free (str_name); } } diff --git a/src/plugins/irc/irc-server.c b/src/plugins/irc/irc-server.c index 50e45f6ea..b99831f6e 100644 --- a/src/plugins/irc/irc-server.c +++ b/src/plugins/irc/irc-server.c @@ -1681,6 +1681,8 @@ irc_server_alloc (const char *name) WEECHAT_HASHTABLE_STRING, NULL, NULL); + new_server->multiline_max_bytes = IRC_SERVER_MULTILINE_DEFAULT_MAX_BYTES; + new_server->multiline_max_lines = IRC_SERVER_MULTILINE_DEFAULT_MAX_LINES; new_server->isupport = NULL; new_server->prefix_modes = NULL; new_server->prefix_chars = NULL; @@ -2749,11 +2751,14 @@ irc_server_outqueue_send (struct t_irc_server *server) { switch (priority) { - case 0: + case 0: /* immediate send */ + anti_flood = 0; + break; + case 1: /* high priority */ anti_flood = IRC_SERVER_OPTION_INTEGER( server, IRC_SERVER_OPTION_ANTI_FLOOD_PRIO_HIGH); break; - default: + default: /* low priority */ anti_flood = IRC_SERVER_OPTION_INTEGER( server, IRC_SERVER_OPTION_ANTI_FLOOD_PRIO_LOW); break; @@ -2950,14 +2955,19 @@ irc_server_send_one_msg (struct t_irc_server *server, int flags, /* get queue from flags */ queue_msg = 0; - if (flags & IRC_SERVER_SEND_OUTQ_PRIO_HIGH) + if (flags & IRC_SERVER_SEND_OUTQ_PRIO_IMMEDIATE) queue_msg = 1; - else if (flags & IRC_SERVER_SEND_OUTQ_PRIO_LOW) + else if (flags & IRC_SERVER_SEND_OUTQ_PRIO_HIGH) queue_msg = 2; + else if (flags & IRC_SERVER_SEND_OUTQ_PRIO_LOW) + queue_msg = 3; switch (queue_msg - 1) { case 0: + anti_flood = 0; + break; + case 1: anti_flood = IRC_SERVER_OPTION_INTEGER( server, IRC_SERVER_OPTION_ANTI_FLOOD_PRIO_HIGH); break; @@ -3090,7 +3100,7 @@ irc_server_sendf (struct t_irc_server *server, int flags, const char *tags, char hash_key[32], *nick, *command, *channel, *new_msg; char str_modifier[128]; const char *str_message, *str_args, *ptr_msg; - int number; + int number, multiline; struct t_hashtable *hashtable; struct t_arraylist *list_messages; @@ -3163,6 +3173,26 @@ irc_server_sendf (struct t_irc_server *server, int flags, const char *tags, hashtable = irc_message_split (server, ptr_msg); if (hashtable) { + multiline = 0; + if (weechat_hashtable_has_key (hashtable, "multiline_args1")) + { + multiline = 1; + if (list_messages) + { + number = 1; + while (1) + { + snprintf (hash_key, sizeof (hash_key), + "multiline_args%d", number); + str_args = weechat_hashtable_get (hashtable, hash_key); + if (!str_args) + break; + weechat_arraylist_add (list_messages, strdup (str_args)); + number++; + } + } + flags |= IRC_SERVER_SEND_OUTQ_PRIO_IMMEDIATE; + } number = 1; while (1) { @@ -3173,8 +3203,7 @@ irc_server_sendf (struct t_irc_server *server, int flags, const char *tags, if (!irc_server_send_one_msg (server, flags, str_message, nick, command, channel, tags)) break; - - if (list_messages) + if (!multiline && list_messages) { snprintf (hash_key, sizeof (hash_key), "args%d", number); str_args = weechat_hashtable_get (hashtable, hash_key); @@ -5620,6 +5649,8 @@ irc_server_disconnect (struct t_irc_server *server, int switch_address, weechat_hashtable_remove_all (server->cap_ls); server->checking_cap_list = 0; weechat_hashtable_remove_all (server->cap_list); + server->multiline_max_bytes = IRC_SERVER_MULTILINE_DEFAULT_MAX_BYTES; + server->multiline_max_lines = IRC_SERVER_MULTILINE_DEFAULT_MAX_LINES; server->is_away = 0; server->away_time = 0; server->lag = 0; @@ -6286,6 +6317,8 @@ irc_server_hdata_server_cb (const void *pointer, void *data, WEECHAT_HDATA_VAR(struct t_irc_server, cap_ls, HASHTABLE, 0, NULL, NULL); WEECHAT_HDATA_VAR(struct t_irc_server, checking_cap_list, INTEGER, 0, NULL, NULL); WEECHAT_HDATA_VAR(struct t_irc_server, cap_list, HASHTABLE, 0, NULL, NULL); + WEECHAT_HDATA_VAR(struct t_irc_server, multiline_max_bytes, INTEGER, 0, NULL, NULL); + WEECHAT_HDATA_VAR(struct t_irc_server, multiline_max_lines, INTEGER, 0, NULL, NULL); WEECHAT_HDATA_VAR(struct t_irc_server, isupport, STRING, 0, NULL, NULL); WEECHAT_HDATA_VAR(struct t_irc_server, prefix_modes, STRING, 0, NULL, NULL); WEECHAT_HDATA_VAR(struct t_irc_server, prefix_chars, STRING, 0, NULL, NULL); @@ -6558,6 +6591,12 @@ irc_server_add_to_infolist (struct t_infolist *infolist, return 0; if (!weechat_infolist_new_var_integer (ptr_item, "checking_cap_list", 0)) return 0; + if (!weechat_infolist_new_var_integer (ptr_item, "multiline_max_bytes", + IRC_SERVER_MULTILINE_DEFAULT_MAX_BYTES)) + return 0; + if (!weechat_infolist_new_var_integer (ptr_item, "multiline_max_lines", + IRC_SERVER_MULTILINE_DEFAULT_MAX_LINES)) + return 0; if (!weechat_infolist_new_var_integer (ptr_item, "is_away", 0)) return 0; if (!weechat_infolist_new_var_string (ptr_item, "away_message", NULL)) @@ -6623,6 +6662,10 @@ irc_server_add_to_infolist (struct t_infolist *infolist, return 0; if (!weechat_hashtable_add_to_infolist (server->cap_list, ptr_item, "cap_list")) return 0; + if (!weechat_infolist_new_var_integer (ptr_item, "multiline_max_bytes", server->multiline_max_bytes)) + return 0; + if (!weechat_infolist_new_var_integer (ptr_item, "multiline_max_lines", server->multiline_max_lines)) + return 0; if (!weechat_infolist_new_var_integer (ptr_item, "is_away", server->is_away)) return 0; if (!weechat_infolist_new_var_string (ptr_item, "away_message", server->away_message)) @@ -7042,6 +7085,8 @@ irc_server_print_log () weechat_log_printf (" cap_list. . . . . . . . . : 0x%lx (hashtable: '%s')", ptr_server->cap_list, weechat_hashtable_get_string (ptr_server->cap_list, "keys_values")); + weechat_log_printf (" multiline_max_bytes . . . : %d", ptr_server->multiline_max_bytes); + weechat_log_printf (" multiline_max_lines . . . : %d", ptr_server->multiline_max_lines); weechat_log_printf (" isupport. . . . . . . . . : '%s'", ptr_server->isupport); weechat_log_printf (" prefix_modes. . . . . . . : '%s'", ptr_server->prefix_modes); weechat_log_printf (" prefix_chars. . . . . . . : '%s'", ptr_server->prefix_chars); diff --git a/src/plugins/irc/irc-server.h b/src/plugins/irc/irc-server.h index f672a5288..2bee2b6d3 100644 --- a/src/plugins/irc/irc-server.h +++ b/src/plugins/irc/irc-server.h @@ -126,16 +126,21 @@ enum t_irc_server_option #define IRC_SERVER_DEFAULT_NICKS "weechat1,weechat2,weechat3,weechat4,weechat5" /* number of queues for sending messages */ -#define IRC_SERVER_NUM_OUTQUEUES_PRIO 2 +#define IRC_SERVER_NUM_OUTQUEUES_PRIO 3 /* flags for irc_server_sendf() */ -#define IRC_SERVER_SEND_OUTQ_PRIO_HIGH (1 << 0) -#define IRC_SERVER_SEND_OUTQ_PRIO_LOW (1 << 1) -#define IRC_SERVER_SEND_RETURN_LIST (1 << 2) +#define IRC_SERVER_SEND_OUTQ_PRIO_IMMEDIATE (1 << 0) +#define IRC_SERVER_SEND_OUTQ_PRIO_HIGH (1 << 1) +#define IRC_SERVER_SEND_OUTQ_PRIO_LOW (1 << 2) +#define IRC_SERVER_SEND_RETURN_LIST (1 << 3) /* version strings */ #define IRC_SERVER_VERSION_CAP "302" +/* multiline default limits */ +#define IRC_SERVER_MULTILINE_DEFAULT_MAX_BYTES 4096 +#define IRC_SERVER_MULTILINE_DEFAULT_MAX_LINES 24 + /* casemapping (string comparisons for nicks/channels) */ enum t_irc_server_casemapping { @@ -232,6 +237,8 @@ struct t_irc_server struct t_hashtable *cap_ls; /* list of supported capabilities */ int checking_cap_list; /* 1 if checking enabled capabilities */ struct t_hashtable *cap_list; /* list of enabled capabilities */ + int multiline_max_bytes; /* max bytes for multiline batch */ + int multiline_max_lines; /* max lines for multiline batch */ char *isupport; /* copy of message 005 (ISUPPORT) */ char *prefix_modes; /* prefix modes from msg 005 (eg "ohv") */ char *prefix_chars; /* prefix chars from msg 005 (eg "@%+") */ @@ -267,9 +274,11 @@ struct t_irc_server time_t last_user_message; /* time of last user message (anti flood)*/ time_t last_away_check; /* time of last away check on server */ time_t last_data_purge; /* time of last purge (some hashtables) */ - struct t_irc_outqueue *outqueue[2]; /* queue for outgoing messages */ + struct t_irc_outqueue *outqueue[IRC_SERVER_NUM_OUTQUEUES_PRIO]; + /* queue for outgoing messages */ /* with 2 priorities (high/low) */ - struct t_irc_outqueue *last_outqueue[2]; /* last outgoing message */ + struct t_irc_outqueue *last_outqueue[IRC_SERVER_NUM_OUTQUEUES_PRIO]; + /* last outgoing message */ struct t_irc_redirect *redirects; /* command redirections */ struct t_irc_redirect *last_redirect; /* last command redirection */ struct t_irc_notify *notify_list; /* list of notify */ diff --git a/src/plugins/irc/irc.c b/src/plugins/irc/irc.c index f545dd603..3f7d7838a 100644 --- a/src/plugins/irc/irc.c +++ b/src/plugins/irc/irc.c @@ -26,6 +26,7 @@ #include "../weechat-plugin.h" #include "irc.h" #include "irc-bar-item.h" +#include "irc-batch.h" #include "irc-buffer.h" #include "irc-channel.h" #include "irc-color.h" @@ -238,6 +239,8 @@ weechat_plugin_init (struct t_weechat_plugin *plugin, int argc, char *argv[]) &irc_tag_modifier_cb, NULL, NULL); weechat_hook_modifier ("irc_tag_unescape_value", &irc_tag_modifier_cb, NULL, NULL); + weechat_hook_modifier ("irc_batch", + &irc_batch_modifier_cb, NULL, NULL); /* hook completions */ irc_completion_init (); diff --git a/tests/unit/plugins/irc/test-irc-batch.cpp b/tests/unit/plugins/irc/test-irc-batch.cpp index ec67143f8..95687638e 100644 --- a/tests/unit/plugins/irc/test-irc-batch.cpp +++ b/tests/unit/plugins/irc/test-irc-batch.cpp @@ -69,6 +69,31 @@ TEST(IrcBatch, Search) irc_server_free (server); } +/* + * Tests functions: + * irc_batch_generate_random_ref + */ + +TEST(IrcBatch, GenerateRandomRef) +{ + char str[16 + 1]; + + strcpy (str, "ABC"); + irc_batch_generate_random_ref (NULL, 8); + irc_batch_generate_random_ref (str, -1); + STRCMP_EQUAL("ABC", str); + + strcpy (str, "ABC"); + irc_batch_generate_random_ref (str, 0); + LONGS_EQUAL(0, strlen (str)); + str[0] = '\0'; + irc_batch_generate_random_ref (str, 8); + LONGS_EQUAL(8, strlen (str)); + str[0] = '\0'; + irc_batch_generate_random_ref (str, 16); + LONGS_EQUAL(16, strlen (str)); +} + /* * Tests functions: * irc_batch_add_to_list @@ -110,6 +135,7 @@ TEST(IrcBatch, StartBatch) STRCMP_EQUAL("params", batch->parameters); CHECK(batch->start_time > 0); POINTERS_EQUAL(NULL, batch->messages); + LONGS_EQUAL(0, batch->end_received); LONGS_EQUAL(0, batch->messages_processed); irc_batch_free (server, batch); @@ -197,6 +223,16 @@ TEST(IrcBatch, EndBatch) /* tested in test-irc-protocol.cpp */ } +/* + * Tests functions: + * irc_batch_process_multiline + */ + +TEST(IrcBatch, ProcessMultiline) +{ + /* tested in test-irc-protocol.cpp */ +} + /* * Tests functions: * irc_batch_hdata_batch_cb diff --git a/tests/unit/plugins/irc/test-irc-message.cpp b/tests/unit/plugins/irc/test-irc-message.cpp index bd9cea529..e5a840248 100644 --- a/tests/unit/plugins/irc/test-irc-message.cpp +++ b/tests/unit/plugins/irc/test-irc-message.cpp @@ -841,6 +841,78 @@ TEST(IrcMessage, ParseToHashtable) hashtable_free (hashtable); } +/* + * Tests functions: + * irc_message_parse_cap_value + */ + +TEST(IrcMessage, ParseCapValue) +{ + struct t_hashtable *hashtable; + + POINTERS_EQUAL(NULL, irc_message_parse_cap_value (NULL)); + + hashtable = irc_message_parse_cap_value (""); + CHECK(hashtable); + LONGS_EQUAL(0, hashtable->items_count); + hashtable_free (hashtable); + + hashtable = irc_message_parse_cap_value ("key1=value1,key2,key3=123"); + CHECK(hashtable); + LONGS_EQUAL(3, hashtable->items_count); + STRCMP_EQUAL("value1", (const char *)hashtable_get (hashtable, "key1")); + POINTERS_EQUAL(NULL, (const char *)hashtable_get (hashtable, "key2")); + STRCMP_EQUAL("123", (const char *)hashtable_get (hashtable, "key3")); + hashtable_free (hashtable); +} + +/* + * Tests functions: + * irc_message_parse_multiline_value + */ + +TEST(IrcMessage, ParseCapMultilineValue) +{ + struct t_irc_server *server; + + server = irc_server_alloc ("test_multiline"); + CHECK(server); + + irc_message_parse_cap_multiline_value (NULL, NULL); + + server->multiline_max_bytes = 0; + server->multiline_max_lines = 0; + irc_message_parse_cap_multiline_value (server, NULL); + LONGS_EQUAL(IRC_SERVER_MULTILINE_DEFAULT_MAX_BYTES, server->multiline_max_bytes); + LONGS_EQUAL(IRC_SERVER_MULTILINE_DEFAULT_MAX_LINES, server->multiline_max_lines); + + server->multiline_max_bytes = 0; + server->multiline_max_lines = 0; + irc_message_parse_cap_multiline_value (server, ""); + LONGS_EQUAL(IRC_SERVER_MULTILINE_DEFAULT_MAX_BYTES, server->multiline_max_bytes); + LONGS_EQUAL(IRC_SERVER_MULTILINE_DEFAULT_MAX_LINES, server->multiline_max_lines); + + server->multiline_max_bytes = 0; + server->multiline_max_lines = 0; + irc_message_parse_cap_multiline_value (server, "max-bytes=2048"); + LONGS_EQUAL(2048, server->multiline_max_bytes); + LONGS_EQUAL(IRC_SERVER_MULTILINE_DEFAULT_MAX_LINES, server->multiline_max_lines); + + server->multiline_max_bytes = 0; + server->multiline_max_lines = 0; + irc_message_parse_cap_multiline_value (server, "max-lines=8"); + LONGS_EQUAL(IRC_SERVER_MULTILINE_DEFAULT_MAX_BYTES, server->multiline_max_bytes); + LONGS_EQUAL(8, server->multiline_max_lines); + + server->multiline_max_bytes = 0; + server->multiline_max_lines = 0; + irc_message_parse_cap_multiline_value (server, "max-bytes=2048,max-lines=8"); + LONGS_EQUAL(2048, server->multiline_max_bytes); + LONGS_EQUAL(8, server->multiline_max_lines); + + irc_server_free (server); +} + char * convert_irc_charset_cb (const void *pointer, void *data, const char *modifier, const char *modifier_data, @@ -1039,6 +1111,8 @@ TEST(IrcMessage, Split) { struct t_irc_server *server; struct t_hashtable *hashtable; + const char *ptr_msg, *pos1, *pos2; + char batch_ref[512], msg[4096]; server = irc_server_alloc ("test_split_msg"); CHECK(server); @@ -1607,6 +1681,254 @@ TEST(IrcMessage, Split) (const char *)hashtable_get (hashtable, "args3")); hashtable_free (hashtable); + /* PRIVMSG with multiline: BATCH is used */ + hashtable_set (server->cap_list, "batch", NULL); + hashtable_set (server->cap_list, "draft/multiline", NULL); + hashtable = irc_message_split (server, "PRIVMSG #channel :test\n\nline 3"); + CHECK(hashtable); + LONGS_EQUAL(12, hashtable->items_count); + STRCMP_EQUAL("5", + (const char *)hashtable_get (hashtable, "count")); + ptr_msg = (const char *)hashtable_get (hashtable, "msg1"); + CHECK(ptr_msg); + STRNCMP_EQUAL("BATCH +", ptr_msg, 7); + pos1 = ptr_msg + 7; + pos2 = strchr (pos1, ' '); + CHECK(pos2); + memcpy (batch_ref, pos1, pos2 - pos1); + batch_ref[pos2 - pos1] = '\0'; + snprintf (msg, sizeof (msg), + "BATCH +%s draft/multiline #channel", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg1")); + snprintf (msg, sizeof (msg), + "+%s draft/multiline #channel", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "args1")); + snprintf (msg, sizeof (msg), + "@batch=%s PRIVMSG #channel :test", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg2")); + STRCMP_EQUAL("test", (const char *)hashtable_get (hashtable, "args2")); + snprintf (msg, sizeof (msg), + "@batch=%s PRIVMSG #channel :", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg3")); + STRCMP_EQUAL("", (const char *)hashtable_get (hashtable, "args3")); + snprintf (msg, sizeof (msg), + "@batch=%s PRIVMSG #channel :line 3", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg4")); + STRCMP_EQUAL("line 3", (const char *)hashtable_get (hashtable, "args4")); + snprintf (msg, sizeof (msg), + "BATCH -%s", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg5")); + snprintf (msg, sizeof (msg), + "-%s", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "args5")); + STRCMP_EQUAL("test\n\nline 3", + (const char *)hashtable_get (hashtable, "multiline_args1")); + hashtable_free (hashtable); + hashtable_remove (server->cap_list, "batch"); + hashtable_remove (server->cap_list, "draft/multiline"); + + /* NOTICE with multiline: BATCH is used */ + hashtable_set (server->cap_list, "batch", NULL); + hashtable_set (server->cap_list, "draft/multiline", NULL); + hashtable = irc_message_split (server, "NOTICE #channel :\ntest\nline 2"); + CHECK(hashtable); + LONGS_EQUAL(12, hashtable->items_count); + STRCMP_EQUAL("5", + (const char *)hashtable_get (hashtable, "count")); + ptr_msg = (const char *)hashtable_get (hashtable, "msg1"); + CHECK(ptr_msg); + STRNCMP_EQUAL("BATCH +", ptr_msg, 7); + pos1 = ptr_msg + 7; + pos2 = strchr (pos1, ' '); + CHECK(pos2); + memcpy (batch_ref, pos1, pos2 - pos1); + batch_ref[pos2 - pos1] = '\0'; + snprintf (msg, sizeof (msg), + "BATCH +%s draft/multiline #channel", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg1")); + snprintf (msg, sizeof (msg), + "+%s draft/multiline #channel", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "args1")); + snprintf (msg, sizeof (msg), + "@batch=%s NOTICE #channel :", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg2")); + STRCMP_EQUAL("", (const char *)hashtable_get (hashtable, "args2")); + snprintf (msg, sizeof (msg), + "@batch=%s NOTICE #channel :test", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg3")); + STRCMP_EQUAL("test", (const char *)hashtable_get (hashtable, "args3")); + snprintf (msg, sizeof (msg), + "@batch=%s NOTICE #channel :line 2", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg4")); + STRCMP_EQUAL("line 2", (const char *)hashtable_get (hashtable, "args4")); + snprintf (msg, sizeof (msg), + "BATCH -%s", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg5")); + snprintf (msg, sizeof (msg), + "-%s", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "args5")); + STRCMP_EQUAL("\ntest\nline 2", + (const char *)hashtable_get (hashtable, "multiline_args1")); + hashtable_free (hashtable); + hashtable_remove (server->cap_list, "batch"); + hashtable_remove (server->cap_list, "draft/multiline"); + + /* PRIVMSG with multiline exceeding "max-lines" */ + server->multiline_max_bytes = IRC_SERVER_MULTILINE_DEFAULT_MAX_BYTES; + server->multiline_max_lines = 3; + hashtable_set (server->cap_list, "batch", NULL); + hashtable_set (server->cap_list, "draft/multiline", NULL); + hashtable = irc_message_split ( + server, + "PRIVMSG #channel :test\nline 2\nline 3\nline 4"); + CHECK(hashtable); + LONGS_EQUAL(19, hashtable->items_count); + STRCMP_EQUAL("8", + (const char *)hashtable_get (hashtable, "count")); + ptr_msg = (const char *)hashtable_get (hashtable, "msg1"); + CHECK(ptr_msg); + STRNCMP_EQUAL("BATCH +", ptr_msg, 7); + pos1 = ptr_msg + 7; + pos2 = strchr (pos1, ' '); + CHECK(pos2); + memcpy (batch_ref, pos1, pos2 - pos1); + batch_ref[pos2 - pos1] = '\0'; + snprintf (msg, sizeof (msg), + "BATCH +%s draft/multiline #channel", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg1")); + snprintf (msg, sizeof (msg), + "+%s draft/multiline #channel", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "args1")); + snprintf (msg, sizeof (msg), + "@batch=%s PRIVMSG #channel :test", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg2")); + STRCMP_EQUAL("test", (const char *)hashtable_get (hashtable, "args2")); + snprintf (msg, sizeof (msg), + "@batch=%s PRIVMSG #channel :line 2", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg3")); + STRCMP_EQUAL("line 2", (const char *)hashtable_get (hashtable, "args3")); + snprintf (msg, sizeof (msg), + "@batch=%s PRIVMSG #channel :line 3", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg4")); + STRCMP_EQUAL("line 3", (const char *)hashtable_get (hashtable, "args4")); + snprintf (msg, sizeof (msg), + "BATCH -%s", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg5")); + snprintf (msg, sizeof (msg), + "-%s", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "args5")); + ptr_msg = (const char *)hashtable_get (hashtable, "msg6"); + CHECK(ptr_msg); + STRNCMP_EQUAL("BATCH +", ptr_msg, 7); + pos1 = ptr_msg + 7; + pos2 = strchr (pos1, ' '); + CHECK(pos2); + memcpy (batch_ref, pos1, pos2 - pos1); + batch_ref[pos2 - pos1] = '\0'; + snprintf (msg, sizeof (msg), + "BATCH +%s draft/multiline #channel", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg6")); + snprintf (msg, sizeof (msg), + "+%s draft/multiline #channel", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "args6")); + snprintf (msg, sizeof (msg), + "@batch=%s PRIVMSG #channel :line 4", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg7")); + STRCMP_EQUAL("line 4", (const char *)hashtable_get (hashtable, "args7")); + snprintf (msg, sizeof (msg), + "BATCH -%s", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg8")); + snprintf (msg, sizeof (msg), + "-%s", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "args8")); + STRCMP_EQUAL("test\nline 2\nline 3", + (const char *)hashtable_get (hashtable, "multiline_args1")); + STRCMP_EQUAL("line 4", + (const char *)hashtable_get (hashtable, "multiline_args2")); + hashtable_free (hashtable); + hashtable_remove (server->cap_list, "batch"); + hashtable_remove (server->cap_list, "draft/multiline"); + + /* PRIVMSG with multiline exceeding "max-bytes" */ + server->multiline_max_bytes = 200; + server->multiline_max_lines = IRC_SERVER_MULTILINE_DEFAULT_MAX_LINES; + hashtable_set (server->cap_list, "batch", NULL); + hashtable_set (server->cap_list, "draft/multiline", NULL); + hashtable = irc_message_split ( + server, + "PRIVMSG #channel :test\n" + "this is a loooooooooooooooong line 2\n" + "this is a loooooooooooooooong line 3\n" + "this is a loooooooooooooooong line 4"); + CHECK(hashtable); + LONGS_EQUAL(19, hashtable->items_count); + STRCMP_EQUAL("8", (const char *)hashtable_get (hashtable, "count")); + ptr_msg = (const char *)hashtable_get (hashtable, "msg1"); + CHECK(ptr_msg); + STRNCMP_EQUAL("BATCH +", ptr_msg, 7); + pos1 = ptr_msg + 7; + pos2 = strchr (pos1, ' '); + CHECK(pos2); + memcpy (batch_ref, pos1, pos2 - pos1); + batch_ref[pos2 - pos1] = '\0'; + snprintf (msg, sizeof (msg), + "BATCH +%s draft/multiline #channel", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg1")); + snprintf (msg, sizeof (msg), + "+%s draft/multiline #channel", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "args1")); + snprintf (msg, sizeof (msg), + "@batch=%s PRIVMSG #channel :test", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg2")); + STRCMP_EQUAL("test", (const char *)hashtable_get (hashtable, "args2")); + snprintf (msg, sizeof (msg), + "@batch=%s PRIVMSG #channel :this is a loooooooooooooooong line 2", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg3")); + STRCMP_EQUAL("this is a loooooooooooooooong line 2", (const char *)hashtable_get (hashtable, "args3")); + snprintf (msg, sizeof (msg), + "BATCH -%s", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg4")); + snprintf (msg, sizeof (msg), + "-%s", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "args4")); + ptr_msg = (const char *)hashtable_get (hashtable, "msg5"); + CHECK(ptr_msg); + STRNCMP_EQUAL("BATCH +", ptr_msg, 7); + pos1 = ptr_msg + 7; + pos2 = strchr (pos1, ' '); + CHECK(pos2); + memcpy (batch_ref, pos1, pos2 - pos1); + batch_ref[pos2 - pos1] = '\0'; + snprintf (msg, sizeof (msg), + "BATCH +%s draft/multiline #channel", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg5")); + snprintf (msg, sizeof (msg), + "+%s draft/multiline #channel", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "args5")); + snprintf (msg, sizeof (msg), + "@batch=%s PRIVMSG #channel :this is a loooooooooooooooong line 3", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg6")); + STRCMP_EQUAL("this is a loooooooooooooooong line 3", (const char *)hashtable_get (hashtable, "args6")); + snprintf (msg, sizeof (msg), + "@batch=%s PRIVMSG #channel :this is a loooooooooooooooong line 4", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg7")); + STRCMP_EQUAL("this is a loooooooooooooooong line 4", (const char *)hashtable_get (hashtable, "args7")); + snprintf (msg, sizeof (msg), + "BATCH -%s", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "msg8")); + snprintf (msg, sizeof (msg), + "-%s", batch_ref); + STRCMP_EQUAL(msg, (const char *)hashtable_get (hashtable, "args8")); + STRCMP_EQUAL("test\n" + "this is a loooooooooooooooong line 2", + (const char *)hashtable_get (hashtable, "multiline_args1")); + STRCMP_EQUAL("this is a loooooooooooooooong line 3\n" + "this is a loooooooooooooooong line 4", + (const char *)hashtable_get (hashtable, "multiline_args2")); + hashtable_free (hashtable); + hashtable_remove (server->cap_list, "batch"); + hashtable_remove (server->cap_list, "draft/multiline"); + /* 005: no split */ hashtable = irc_message_split (server, "005 nick " MSG_005); CHECK(hashtable); diff --git a/tests/unit/plugins/irc/test-irc-protocol.cpp b/tests/unit/plugins/irc/test-irc-protocol.cpp index 849dcc08f..9e59f809d 100644 --- a/tests/unit/plugins/irc/test-irc-protocol.cpp +++ b/tests/unit/plugins/irc/test-irc-protocol.cpp @@ -67,8 +67,8 @@ extern char *irc_protocol_cap_to_enable (const char *capabilities, "CHANTYPES=# CHANMODES=eIbq,k,flj,CFLMPQScgimnprstuz " \ "MONITOR=100" #define IRC_ALL_CAPS "account-notify,away-notify,batch,cap-notify," \ - "chghost,extended-join,invite-notify,message-tags,multi-prefix," \ - "server-time,setname,userhost-in-names" + "chghost,draft/multiline,extended-join,invite-notify,message-tags," \ + "multi-prefix,server-time,setname,userhost-in-names" #define WEE_CHECK_CAP_TO_ENABLE(__result, __string, __sasl_requested) \ str = irc_protocol_cap_to_enable (__string, __sasl_requested); \ @@ -804,6 +804,10 @@ TEST(IrcProtocolWithServer, batch) SRV_INIT_JOIN2; + /* assume "batch" and "draft/multiline" capabilities are enabled in server */ + hashtable_set (ptr_server->cap_list, "batch", NULL); + hashtable_set (ptr_server->cap_list, "draft/multiline", NULL); + /* not enough parameters */ RECV(":server BATCH"); CHECK_ERROR_PARAMS("batch", 0, 1); @@ -917,6 +921,29 @@ TEST(IrcProtocolWithServer, batch) CHECK_CHAN("bob test ref2"); POINTERS_EQUAL(NULL, irc_batch_search (ptr_server, "ref1")); POINTERS_EQUAL(NULL, irc_batch_search (ptr_server, "ref2")); + + /* multiline */ + RECV(":server BATCH +ref draft/multiline #test"); + CHECK_NO_MSG; + RECV("@batch=ref :bob!user_b@host_b PRIVMSG #test :line 1"); + CHECK_NO_MSG; + RECV("@batch=ref :bob!user_b@host_b PRIVMSG #test :line 2"); + CHECK_NO_MSG; + RECV(":server BATCH -ref"); + CHECK_CHAN("bob line 1\n" + "line 2"); + + /* multiline with CTCP */ + RECV(":server BATCH +ref draft/multiline #test"); + CHECK_NO_MSG; + RECV("@batch=ref :bob!user_b@host_b PRIVMSG #test :" + "\x01" "ACTION is testing"); + CHECK_NO_MSG; + RECV("@batch=ref :bob!user_b@host_b PRIVMSG #test :again" "\x01"); + CHECK_NO_MSG; + RECV(":server BATCH -ref"); + CHECK_CHAN(" * bob is testing\n" + "again"); } /*