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,11 +607,14 @@ 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 (! peer) {
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) {
@ -623,18 +626,17 @@ static int tgprpl_send_im (PurpleConnection *gc, const char *who, const char *me
return -1;
}
// give a proper error message when attempting to send to a secret chat you don't own
// 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;
}
return tgp_msg_send (gc_get_tls (gc), message, peer->id);
}
// 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);
warning ("peer not found");
return -1;
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);
}

198
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));
@ -277,84 +257,126 @@ void send_inline_picture_done (struct tgl_state *TLS, void *extra, int success,
}
}
static GList *tgp_msg_imgs_parse (const char *msg) {
GList *imgs = NULL;
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;
int e = i;
while (msg[++ e] != '>' && e < len) {}
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();
}
}
} 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;
}
}
html[j] = 0;
return html;
}
int tgp_msg_send (struct tgl_state *TLS, const char *message, tgl_peer_id_t to) {
#ifndef __ADIUM_
// search for outgoing embedded image tags and send them
gchar *img = NULL;
gchar *stripped = NULL;
debug ("tgp_msg_send='%s'", message);
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;
}
// 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) {
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;
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);
}
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);
// replace markdown with html
char *html = g_strstrip(tgp_msg_markdown_convert (message));
// 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;
}
// 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
return 0;
} else {
failure ("Storing %s in imagestore failed: %s\n", tmp, err->message);
g_error_free (err);
return -1;
}
}
}
// 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.
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);
// 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);
g_free (unescaped);
g_free (stripped);
return ret;
#endif
// when the other peer receives a message it is obvious that the previous messages were read
pending_reads_send_user (TLS, to);
return tgp_msg_send_split (TLS, message, to);
// 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;
if (C->msg) {
g_free (C->msg);
}
free (C);
}