Support sending code tags and improve inline image handling

Properly parse inline img tags in messages to support multiple inline messages in one message.
Adium: remove adium-specific code path and allow embedding markup in outgoing message
This commit is contained in:
mjentsch 2016-03-27 01:59:46 +01:00
parent 7205b4df1b
commit a35c6cdd3b
5 changed files with 160 additions and 112 deletions

View file

@ -22,6 +22,7 @@
#import <Adium/ESFileTransfer.h>
#import <Adium/AIListContact.h>
#import <Adium/AIMenuControllerProtocol.h>
#import <Adium/AIHTMLDecoder.h>
#import <AIUtilities/AIMenuAdditions.h>
#include "telegram-purple.h"
@ -182,6 +183,24 @@
[super cancelFileTransfer:fileTransfer];
}
- (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forListObject:(AIListObject *)inListObject
{
static AIHTMLDecoder *htmlEncoder = nil;
if (!htmlEncoder) {
htmlEncoder = [[AIHTMLDecoder alloc] init];
[htmlEncoder setIncludesHeaders:NO];
[htmlEncoder setIncludesFontTags:NO];
[htmlEncoder setClosesFontTags:NO];
[htmlEncoder setIncludesStyleTags:YES];
[htmlEncoder setIncludesColorTags:NO];
[htmlEncoder setEncodesNonASCII:NO];
[htmlEncoder setPreservesAllSpaces:NO];
[htmlEncoder setUsesAttachmentTextEquivalents:YES];
}
return [htmlEncoder encodeHTML:inAttributedString imagesPath:nil];
}
#pragma mark Group Chats
/*!

View file

@ -607,34 +607,36 @@ static int tgprpl_send_im (PurpleConnection *gc, const char *who, const char *me
return 1;
}
// Make sure that to only send messages to an existing peer by searching it in the peer tree, to give immediate
// feedback by returning an error-code in case the peer doesn't exist.
// check receiver to give immediate feedback in case sending a message is not possible
tgl_peer_t *peer = tgp_blist_lookup_peer_get (gc_get_tls (gc), who);
if (peer) {
// give a proper error message when attempting to send to a secret chat that is not usable
if (tgl_get_peer_type (peer->id) == TGL_PEER_ENCR_CHAT && peer->encr_chat.state != sc_ok) {
const char *msg;
if (peer->encr_chat.state == sc_deleted) {
msg = _("Secret chat was already deleted");
} else {
msg = _("Secret chat is not ready");
}
tgp_msg_special_out (gc_get_tls (gc), msg, peer->id, PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_ERROR);
return -1;
}
// give a proper error message when attempting to send to a secret chat you don't own
if (tgl_get_peer_type (peer->id) == TGL_PEER_CHANNEL && ! (peer->flags & TGLCHF_CREATOR)) {
tgp_msg_special_out (gc_get_tls (gc), _("Only the creator of a channel can post messages."), peer->id,
PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_ERROR);
return -1;
}
return tgp_msg_send (gc_get_tls (gc), message, peer->id);
if (! peer) {
warning ("peer not found");
return -1;
}
warning ("peer not found");
return -1;
// secret chat not yet usable
if (tgl_get_peer_type (peer->id) == TGL_PEER_ENCR_CHAT && peer->encr_chat.state != sc_ok) {
const char *msg;
if (peer->encr_chat.state == sc_deleted) {
msg = _("Secret chat was already deleted");
} else {
msg = _("Secret chat is not ready");
}
tgp_msg_special_out (gc_get_tls (gc), msg, peer->id, PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_ERROR);
return -1;
}
// channel owned by someone else (TEST: why does it work with supergroups?)
if (tgl_get_peer_type (peer->id) == TGL_PEER_CHANNEL && ! (peer->flags & TGLCHF_CREATOR)) {
tgp_msg_special_out (gc_get_tls (gc), _("Only the creator of a channel can post messages."), peer->id,
PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_ERROR);
return -1;
}
// when the other peer receives a message it is obvious that the previous messages were read
pending_reads_send_user (gc_get_tls (gc), peer->id);
return tgp_msg_send (gc_get_tls (gc), message, peer->id);
}
static unsigned int tgprpl_send_typing (PurpleConnection *gc, const char *who, PurpleTypingState typing) {

View file

@ -209,6 +209,9 @@ int tgprpl_send_chat (PurpleConnection *gc, int id, const char *message, PurpleM
}
g_return_val_if_fail(P != NULL, -1);
// when the group receives a message it is obvious that the previous messages were read
pending_reads_send_user (gc_get_tls (gc), P->id);
return tgp_msg_send (gc_get_tls (gc), message, P->id);
}

196
tgp-msg.c
View file

@ -220,34 +220,14 @@ static gboolean tgp_msg_send_schedule_cb (gpointer data) {
return FALSE;
}
static void tgp_msg_send_schedule (struct tgl_state *TLS, gchar *chunk, tgl_peer_id_t to) {
g_queue_push_tail (tls_get_data (TLS)->out_messages, tgp_msg_sending_init (TLS, chunk, to));
static void tgp_msg_send_schedule (struct tgl_state *TLS, const char *chunk, tgl_peer_id_t to) {
g_queue_push_tail (tls_get_data (TLS)->out_messages, tgp_msg_sending_init (TLS, g_strdup (chunk), to));
if (tls_get_data (TLS)->out_timer) {
purple_timeout_remove (tls_get_data (TLS)->out_timer);
}
tls_get_data (TLS)->out_timer = purple_timeout_add (0, tgp_msg_send_schedule_cb, tls_get_data (TLS));
}
static int tgp_msg_send_split (struct tgl_state *TLS, const char *message, tgl_peer_id_t to) {
int size = (int)g_utf8_strlen (message, -1), start = 0;
if (size > TGP_MAX_MSG_SIZE * TGP_DEFAULT_MAX_MSG_SPLIT_COUNT) {
return -E2BIG;
}
while (size > start) {
int end = start + (int)TGP_MAX_MSG_SIZE;
if (end > size) {
end = size;
}
gchar *chunk = g_utf8_substring (message, start, end);
tgp_msg_send_schedule (TLS, chunk, to);
start = end;
}
return 1;
}
void tgp_msg_special_out (struct tgl_state *TLS, const char *msg, tgl_peer_id_t to_id, int flags) {
if (tgl_get_peer_type (to_id) == TGL_PEER_CHAT) {
tgp_chat_got_in (TLS, tgl_peer_get (TLS, to_id), to_id, msg, flags, time(0));
@ -271,90 +251,132 @@ void send_inline_picture_done (struct tgl_state *TLS, void *extra, int success,
char *errormsg = g_strdup_printf ("%d: %s", TLS->error_code, TLS->error);
failure (errormsg);
purple_notify_message (_telegram_protocol, PURPLE_NOTIFY_MSG_ERROR, _("Sending image failed."),
errormsg, NULL, NULL, NULL);
errormsg, NULL, NULL, NULL);
g_free (errormsg);
return;
}
}
int tgp_msg_send (struct tgl_state *TLS, const char *message, tgl_peer_id_t to) {
static GList *tgp_msg_imgs_parse (const char *msg) {
GList *imgs = NULL;
#ifndef __ADIUM_
// search for outgoing embedded image tags and send them
gchar *img = NULL;
gchar *stripped = NULL;
debug ("tgp_msg_send='%s'", message);
int i;
int len = (int) strlen (msg);
for (i = 0; i < len; i ++) {
if (len - i >= 4 && (! memcmp (msg + i, "<IMG", 4) || ! memcmp (msg + i, "<img", 4))) {
i += 4;
if ((img = g_strrstr (message, "<IMG")) || (img = g_strrstr (message, "<img"))) {
if (tgl_get_peer_type(to) == TGL_PEER_ENCR_CHAT) {
tgp_msg_special_out (TLS, _("Sorry, sending documents to encrypted chats not yet supported."), to,
PURPLE_MESSAGE_ERROR | PURPLE_MESSAGE_SYSTEM);
return 0;
}
debug ("img found: %s", img);
gchar *id;
if ((id = g_strrstr (img, "ID=\"")) || (id = g_strrstr (img, "id=\""))) {
id += 4;
int imgid = atoi (id);
if (imgid > 0) {
PurpleStoredImage *psi = purple_imgstore_find_by_id (imgid);
if (! psi) {
failure ("Img %d not found in imgstore", imgid);
return -1;
}
gchar *tmp = g_build_filename (g_get_tmp_dir(), purple_imgstore_get_filename (psi), NULL) ;
GError *err = NULL;
gconstpointer data = purple_imgstore_get_data (psi);
g_file_set_contents (tmp, data, purple_imgstore_get_size (psi), &err);
if (! err) {
int e = i;
while (msg[++ e] != '>' && e < len) {}
unsigned long long int flags = TGL_SEND_MSG_FLAG_DOCUMENT_AUTO;
if (tgl_get_peer_id (to) == TGL_PEER_CHANNEL) {
flags |= TGLMF_POST_AS_CHANNEL;
gchar *id = NULL;
if ((id = g_strstr_len (msg + i, e - i, "ID=\"")) || (id = g_strstr_len (msg + i, e - i, "id=\""))) {
int img = atoi (id + 4);
debug ("parsed img id %d", img);
if (img > 0) {
PurpleStoredImage *psi = purple_imgstore_find_by_id (img);
if (psi) {
imgs = g_list_append (imgs, psi);
} else {
g_warn_if_reached();
}
stripped = g_strstrip(purple_markup_strip_html (message));
tgl_do_send_document (TLS, to, tmp, stripped, (int)strlen (stripped), flags, send_inline_picture_done, NULL);
g_free (stripped);
// return 0 to assure that the picture is not echoed, since
// it will already be echoed with the outgoing message
return 0;
} else {
failure ("Storing %s in imagestore failed: %s\n", tmp, err->message);
g_error_free (err);
return -1;
}
} else {
g_warn_if_reached();
}
i = e;
}
}
return imgs;
}
static char *tgp_msg_markdown_convert (const char *msg) {
int len = (int) strlen (msg);
char *html = g_new0(gchar, 3 * len);
int open = FALSE;
int i, j;
for (i = 0, j = 0; i < len; i ++) {
// markdown for bold and italic doesn't seem to work with non-bots,
// therefore only parse code-tags
if (len - i < 3 || (memcmp (msg + i, "```", 3))) {
html[j ++] = msg[i];
} else {
i += 2;
if (! open) {
assert(j + 6 < 3 * len);
memcpy(html + j, "<code>", 6);
j += 6;
} else {
assert(j + 7 < 3 * len);
memcpy(html + j, "</code>", 7);
j += 7;
}
open = ! open;
}
// no image id found in image
return -1;
}
/*
Adium won't escape any HTML markup and just pass any user-input through,
while Pidgin will replace special chars with the escape chars and also add
additional markup for RTL languages and such.
html[j] = 0;
return html;
}
First, we remove any HTML markup added by Pidgin, since Telegram won't handle it properly.
User-entered HTML is still escaped and therefore won't be harmed.
*/
stripped = purple_markup_strip_html (message);
int tgp_msg_send (struct tgl_state *TLS, const char *message, tgl_peer_id_t to) {
// now unescape the markup, so that html special chars will still show
// up properly in Telegram
gchar *unescaped = purple_unescape_text (stripped);
int ret = tgp_msg_send_split (TLS, stripped, to);
// send all inline images
GList *imgs = tgp_msg_imgs_parse (message);
debug ("parsed %d images in messages", g_list_length (imgs));
while (imgs) {
PurpleStoredImage *psi = imgs->data;
gchar *tmp = g_build_filename (g_get_tmp_dir(), purple_imgstore_get_filename (psi), NULL) ;
GError *err = NULL;
gconstpointer data = purple_imgstore_get_data (psi);
g_file_set_contents (tmp, data, purple_imgstore_get_size (psi), &err);
if (! err) {
debug ("sending img='%s'", tmp);
tgl_do_send_document (TLS, to, tmp, NULL, 0,
TGL_SEND_MSG_FLAG_DOCUMENT_AUTO | (tgl_get_peer_type (to) == TGL_PEER_CHANNEL) ? TGLMF_POST_AS_CHANNEL : 0,
send_inline_picture_done, NULL);
} else {
failure ("error=%s", err->message);
g_warn_if_reached();
}
imgs = g_list_next(imgs);
}
g_free (unescaped);
g_free (stripped);
return ret;
#endif
// replace markdown with html
char *html = g_strstrip(tgp_msg_markdown_convert (message));
// when the other peer receives a message it is obvious that the previous messages were read
pending_reads_send_user (TLS, to);
// check message length
int size = (int) g_utf8_strlen (html, -1);
if (size == 0) {
g_free (html);
return 0; // fail quietly on empty messages
}
if (size > TGP_MAX_MSG_SIZE * TGP_DEFAULT_MAX_MSG_SPLIT_COUNT) {
g_free (html);
return -E2BIG;
}
return tgp_msg_send_split (TLS, message, to);
// send big message as multiple chunks
int start = 0;
while (size > start) {
int end = start + (int)TGP_MAX_MSG_SIZE;
if (end > size) {
end = size;
}
char *chunk = g_utf8_substring (html, start, end);
tgp_msg_send_schedule (TLS, chunk, to);
start = end;
}
g_free (html);
// return 0 to assure that the picture is not echoed, since
// it will already be echoed with the outgoing message
// FIXME: Eventually never display outgoing messages? What about Adium???
return 1;
}
static char *tgp_msg_photo_display (struct tgl_state *TLS, const char *filename, int *flags) {

View file

@ -79,7 +79,7 @@ struct tgp_msg_loading *tgp_msg_loading_init (struct tgl_message *M) {
return C;
}
struct tgp_msg_sending *tgp_msg_sending_init (struct tgl_state *TLS, gchar *M, tgl_peer_id_t to) {
struct tgp_msg_sending *tgp_msg_sending_init (struct tgl_state *TLS, char *M, tgl_peer_id_t to) {
struct tgp_msg_sending *C = malloc (sizeof (struct tgp_msg_sending));
C->TLS = TLS;
C->msg = M;
@ -89,7 +89,9 @@ struct tgp_msg_sending *tgp_msg_sending_init (struct tgl_state *TLS, gchar *M, t
void tgp_msg_sending_free (gpointer data) {
struct tgp_msg_sending *C = data;
g_free (C->msg);
if (C->msg) {
g_free (C->msg);
}
free (C);
}