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:
parent
7205b4df1b
commit
a35c6cdd3b
5 changed files with 160 additions and 112 deletions
|
@ -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
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
196
tgp-msg.c
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue