2 * MXit Protocol libPurple Plugin
4 * -- MXit Forms & Commands --
6 * Andrew Victor <libpurple@mxit.com>
8 * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
9 * <http://www.mxitlifestyle.com>
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
30 #include "image-store.h"
37 #undef MXIT_DEBUG_COMMANDS
40 * the MXit Command identifiers
44 MXIT_CMD_UNKNOWN
= 0, /* Unknown command */
45 MXIT_CMD_CLEAR
, /* Clear (clear) */
46 MXIT_CMD_SENDSMS
, /* Send SMS (sendsms) */
47 MXIT_CMD_REPLY
, /* Reply (reply) */
48 MXIT_CMD_PLATREQ
, /* Platform Request (platreq) */
49 MXIT_CMD_SELECTCONTACT
, /* Select Contact (selc) */
50 MXIT_CMD_IMAGE
, /* Inline image (img) */
51 MXIT_CMD_SCREENCONFIG
, /* Chat-screen config (csc) */
52 MXIT_CMD_SCREENINFO
, /* Chat-screen info (csi) */
53 MXIT_CMD_IMAGESTRIP
, /* Image Strip (is) */
54 MXIT_CMD_TABLE
/* Table (tbl) */
57 /* Chat-screen behaviours (bhvr) */
58 #define SCREEN_NO_HEADINGS 0x01
59 #define SCREEN_FULLSCREEN 0x02
60 #define SCREEN_AUTOCLEAR 0x04
61 #define SCREEN_NO_AUDIO 0x08
62 #define SCREEN_NO_MSGPREFIX 0x10
63 #define SCREEN_NOTIFY 0x20
64 #define SCREEN_PROGRESSBAR 0x40
68 * object for an inline image request with an URL
77 /*------------------------------------------------------------------------
78 * Callback function invoked when an inline image request to a web site completes.
81 mxit_cb_ii_returned(PurpleHttpConnection
*http_conn
, PurpleHttpResponse
*response
,
84 struct ii_url_request
* iireq
= _iireq
;
89 #ifdef MXIT_DEBUG_COMMANDS
90 purple_debug_info(MXIT_PLUGIN_ID
, "Inline Image returned from %s\n", iireq
->url
);
93 if (!purple_http_response_is_successful(response
)) {
94 /* no reply from the WAP site */
95 purple_debug_error(MXIT_PLUGIN_ID
, "Error downloading Inline Image from %s.\n", iireq
->url
);
99 /* lets first see if we don't have the inline image already in cache */
100 if (g_hash_table_lookup(iireq
->mx
->session
->inline_images
, iireq
->url
)) {
101 /* inline image found in the cache, so we just ignore this reply */
105 /* we now have the inline image, store a copy in the imagestore */
106 data
= purple_http_response_get_data(response
, &len
);
107 img
= purple_image_new_from_data(g_memdup(data
, len
), len
);
108 g_hash_table_insert(iireq
->mx
->session
->inline_images
, iireq
->url
, img
);
110 iireq
->mx
->flags
|= PURPLE_MESSAGE_IMAGES
;
113 iireq
->mx
->img_count
--;
114 if ((iireq
->mx
->img_count
== 0) && (iireq
->mx
->converted
)) {
116 * this was the last outstanding emoticon for this message,
117 * so we can now display it to the user.
119 mxit_show_message(iireq
->mx
);
126 /*------------------------------------------------------------------------
127 * Return the command identifier of this MXit Command.
129 * @param cmd The MXit command <key,value> map
130 * @return The MXit command identifier
132 static MXitCommandType
command_type(GHashTable
* hash
)
137 op
= g_hash_table_lookup(hash
, "op");
139 if ( strcmp(op
, "cmd") == 0 ) {
140 type
= g_hash_table_lookup(hash
, "type");
141 if (type
== NULL
) /* no command provided */
142 return MXIT_CMD_UNKNOWN
;
143 else if (strcmp(type
, "clear") == 0) /* clear */
144 return MXIT_CMD_CLEAR
;
145 else if (strcmp(type
, "sendsms") == 0) /* send an SMS */
146 return MXIT_CMD_SENDSMS
;
147 else if (strcmp(type
, "reply") == 0) /* list of options */
148 return MXIT_CMD_REPLY
;
149 else if (strcmp(type
, "platreq") == 0) /* platform request */
150 return MXIT_CMD_PLATREQ
;
151 else if (strcmp(type
, "selc") == 0) /* select contact */
152 return MXIT_CMD_SELECTCONTACT
;
154 else if (strcmp(op
, "img") == 0) /* inline image */
155 return MXIT_CMD_IMAGE
;
156 else if (strcmp(op
, "csc") == 0) /* chat-screen config */
157 return MXIT_CMD_SCREENCONFIG
;
158 else if (strcmp(op
, "csi") == 0) /* chat-screen info */
159 return MXIT_CMD_SCREENINFO
;
160 else if (strcmp(op
, "is") == 0) /* image-strip */
161 return MXIT_CMD_IMAGESTRIP
;
162 else if (strcmp(op
, "tbl") == 0) /* table */
163 return MXIT_CMD_TABLE
;
166 return MXIT_CMD_UNKNOWN
;
170 /*------------------------------------------------------------------------
171 * Tokenize a MXit Command string into a <key,value> map.
173 * @param cmd The MXit command string
174 * @return The <key,value> hash-map, or NULL on error.
176 static GHashTable
* command_tokenize(char* cmd
)
178 GHashTable
* hash
= NULL
;
182 #ifdef MXIT_DEBUG_COMMANDS
183 purple_debug_info(MXIT_PLUGIN_ID
, "command: '%s'\n", cmd
);
186 /* explode the command into parts */
187 parts
= g_strsplit(cmd
, "|", 0);
189 hash
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
191 /* now break part into a key & value */
192 while (parts
[i
] != NULL
) {
195 value
= strchr(parts
[i
], '='); /* find start of value */
201 #ifdef MXIT_DEBUG_COMMANDS
202 purple_debug_info(MXIT_PLUGIN_ID
, " key='%s' value='%s'\n", parts
[i
], value
);
205 g_hash_table_insert(hash
, g_strdup(parts
[i
]), g_strdup(value
));
216 /*------------------------------------------------------------------------
217 * Process a Clear MXit command.
218 * [::op=cmd|type=clear|clearmsgscreen=true|auto=true|id=12345:]
220 * @param session The MXit session object
221 * @param from The sender of the message.
222 * @param hash The MXit command <key,value> map
224 static void command_clear(struct MXitSession
* session
, const char* from
, GHashTable
* hash
)
226 PurpleIMConversation
*im
;
227 char* clearmsgscreen
;
229 im
= purple_conversations_find_im_with_account(from
, session
->acc
);
231 purple_debug_error(MXIT_PLUGIN_ID
, _( "Conversation with '%s' not found\n" ), from
);
235 clearmsgscreen
= g_hash_table_lookup(hash
, "clearmsgscreen");
236 if ( (clearmsgscreen
) && (strcmp(clearmsgscreen
, "true") == 0) ) {
237 /* this is a command to clear the chat screen */
238 purple_conversation_clear_message_history(PURPLE_CONVERSATION(im
));
243 /*------------------------------------------------------------------------
244 * Process a Reply MXit command.
245 * [::op=cmd|type=reply|replymsg=back|selmsg=b) Back|displaymsg=Processing|id=12345:]
246 * [::op=cmd|nm=rep|type=reply|replymsg=back|selmsg=b) Back|displaymsg=Processing|id=12345:]
248 * @param mx The received message data object
249 * @param hash The MXit command <key,value> map
251 static void command_reply(struct RXMsgData
* mx
, GHashTable
* hash
)
257 selmsg
= g_hash_table_lookup(hash
, "selmsg"); /* selection message */
258 replymsg
= g_hash_table_lookup(hash
, "replymsg"); /* reply message */
259 nm
= g_hash_table_lookup(hash
, "nm"); /* name parameter */
261 if ((selmsg
== NULL
) || (replymsg
== NULL
))
262 return; /* these parameters are required */
264 if (nm
) { /* indicates response must be a structured response */
265 gchar
* seltext
= g_markup_escape_text(purple_url_decode(selmsg
), -1);
266 gchar
* replycmd
= g_strdup_printf("type=reply|nm=%s|res=%s|err=0", nm
, purple_url_decode(replymsg
));
268 mxit_add_html_link( mx
, replycmd
, TRUE
, seltext
);
274 gchar
* seltext
= g_markup_escape_text(purple_url_decode(selmsg
), -1);
276 mxit_add_html_link( mx
, purple_url_decode(replymsg
), FALSE
, seltext
);
283 /*------------------------------------------------------------------------
284 * Process a PlatformRequest MXit command.
285 * [::op=cmd|type=platreq|selmsg=Upgrade MXit|dest=http%3a//m.mxit.com|id=12345:]
287 * @param hash The MXit command <key,value> map
288 * @param msg The message to display (as generated so far)
290 static void command_platformreq(GHashTable
* hash
, GString
* msg
)
296 selmsg
= g_hash_table_lookup(hash
, "selmsg"); /* find the selection message */
297 if (selmsg
&& (strlen(selmsg
) > 0)) {
298 text
= g_markup_escape_text(purple_url_decode(selmsg
), -1);
301 dest
= g_hash_table_lookup(hash
, "dest"); /* find the destination */
303 g_string_append_printf(msg
, "<a href=\"%s\">%s</a>", purple_url_decode(dest
), (text
) ? text
: _( "Download" )); /* add link to display message */
310 /*------------------------------------------------------------------------
311 * Process an inline image MXit command.
312 * [::op=img|dat=ASDF23408asdflkj2309flkjsadf%3d%3d|algn=1|w=120|h=12|t=100|replymsg=text:]
314 * @param mx The received message data object
315 * @param hash The MXit command <key,value> map
316 * @param msg The message to display (as generated so far)
318 static void command_image(struct RXMsgData
* mx
, GHashTable
* hash
, GString
* msg
)
325 img
= g_hash_table_lookup(hash
, "dat");
330 rawimg
= purple_base64_decode(img
, &rawimglen
);
331 //purple_util_write_data_to_file_absolute("/tmp/mxitinline.png", (char*) rawimg, rawimglen);
332 pimg
= purple_image_new_from_data(rawimg
, rawimglen
);
333 pimg_id
= purple_image_store_add(pimg
);
334 g_string_append_printf(msg
, "<img src=\""
335 PURPLE_IMAGE_STORE_PROTOCOL
"%u\">", pimg_id
);
336 mx
->flags
|= PURPLE_MESSAGE_IMAGES
;
339 img
= g_hash_table_lookup(hash
, "src");
341 struct ii_url_request
* iireq
;
343 iireq
= g_new0(struct ii_url_request
,1);
344 iireq
->url
= g_strdup(purple_url_decode(img
));
347 g_string_append_printf(msg
, "%s%s>", MXIT_II_TAG
, iireq
->url
);
350 /* lets first see if we don't have the inline image already in cache */
351 if (g_hash_table_lookup(mx
->session
->inline_images
, iireq
->url
)) {
352 /* inline image found in the cache, so we do not have to request it from the web */
356 /* send the request for the inline image */
357 purple_debug_info(MXIT_PLUGIN_ID
, "sending request for inline image '%s'\n", iireq
->url
);
359 purple_http_get(mx
->session
->con
, mxit_cb_ii_returned
, iireq
, iireq
->url
);
365 /* if this is a clickable image, show a click link */
366 reply
= g_hash_table_lookup(hash
, "replymsg");
368 g_string_append_printf(msg
, "\n");
369 mxit_add_html_link(mx
, purple_url_decode(reply
), FALSE
, _( "click here" ));
374 /*------------------------------------------------------------------------
375 * Process an Imagestrip MXit command.
376 * [::op=is|nm=status|dat=iVBORw0KGgoAAAA%3d%3d|v=63398792426788|fw=8|fh=8|layer=0:]
378 * @param from The sender of the message.
379 * @param hash The MXit command <key,value> map
381 static void command_imagestrip(struct MXitSession
* session
, const char* from
, GHashTable
* hash
)
384 const char* validator
;
386 int width
, height
, layer
;
388 purple_debug_info(MXIT_PLUGIN_ID
, "ImageStrip received from %s\n", from
);
390 /* image strip name */
391 name
= g_hash_table_lookup(hash
, "nm");
394 validator
= g_hash_table_lookup(hash
, "v");
396 if (!name
|| !validator
)
400 tmp
= g_hash_table_lookup(hash
, "dat");
410 /* base64 decode the image data */
411 rawimg
= purple_base64_decode(tmp
, &rawimglen
);
415 /* save it to a file */
416 dir
= g_build_filename(purple_user_dir(), "mxit", "imagestrips", NULL
);
417 purple_build_dir(dir
, S_IRUSR
| S_IWUSR
| S_IXUSR
); /* ensure directory exists */
419 escfrom
= g_strdup(purple_escape_filename(from
));
420 escname
= g_strdup(purple_escape_filename(name
));
421 escvalidator
= g_strdup(purple_escape_filename(validator
));
422 filename
= g_strdup_printf("%s" G_DIR_SEPARATOR_S
"%s-%s-%s.png", dir
, escfrom
, escname
, escvalidator
);
424 purple_util_write_data_to_file_absolute(filename
, (char*) rawimg
, rawimglen
);
429 g_free(escvalidator
);
433 tmp
= g_hash_table_lookup(hash
, "fw");
434 width
= (tmp
? atoi(tmp
) : 0);
436 tmp
= g_hash_table_lookup(hash
, "fh");
437 height
= (tmp
? atoi(tmp
) : 0);
439 tmp
= g_hash_table_lookup(hash
, "layer");
440 layer
= (tmp
? atoi(tmp
) : 0);
442 purple_debug_info(MXIT_PLUGIN_ID
, "ImageStrip %s from %s: [w=%i h=%i l=%i validator=%s]\n", name
, from
, width
, height
, layer
, validator
);
446 /*------------------------------------------------------------------------
447 * Process a Chat-Screen-Info MXit command.
450 * @param session The MXit session object
451 * @param from The sender of the message.
453 static void command_screeninfo(struct MXitSession
* session
, const char* from
)
457 purple_debug_info(MXIT_PLUGIN_ID
, "Chat Screen Info received from %s\n", from
);
459 // TODO: Determine width, height, colors of chat-screen.
461 response
= g_strdup_printf("::type=csi|res=bhvr,0;w,%i;h,%i;col,0.ffffffff,29.ff000000:", 300, 400);
463 /* send response back to MXit */
464 mxit_send_message( session
, from
, response
, FALSE
, TRUE
);
470 /*------------------------------------------------------------------------
471 * Process a Chat-Screen-Configure MXit command.
472 * [::op=csc|bhvr=|menu=<menu>|col=<colors>:]
474 * menu ::= <menuitem> { ";" <menuitem> }
475 * menuitem ::= { type "," <text> "," <name> "," <meta> }
476 * colors ::= <color> { ";" <color> }
477 * color ::= <colorid> "," <ARGB hex color>
479 * @param session The MXit session object
480 * @param from The sender of the message.
481 * @param hash The MXit command <key,value> map
483 static void command_screenconfig(struct MXitSession
* session
, const char* from
, GHashTable
* hash
)
487 purple_debug_info(MXIT_PLUGIN_ID
, "Chat Screen Configure received from %s\n", from
);
490 tmp
= g_hash_table_lookup(hash
, "bhvr");
492 purple_debug_info(MXIT_PLUGIN_ID
, " behaviour = %s\n", tmp
);
493 // TODO: Re-configure conversation screen.
497 tmp
= g_hash_table_lookup(hash
, "menu");
499 purple_debug_info(MXIT_PLUGIN_ID
, " menu = %s\n", tmp
);
500 // TODO: Implement conversation-specific sub-menu.
504 tmp
= g_hash_table_lookup(hash
, "col");
506 purple_debug_info(MXIT_PLUGIN_ID
, " colours = %s\n", tmp
);
507 // TODO: Re-configuration conversation colors.
512 /*------------------------------------------------------------------------
513 * Process a Table Markup MXit command.
515 * @param mx The received message data object
516 * @param hash The MXit command <key,value> map
518 static void command_table(struct RXMsgData
* mx
, GHashTable
* hash
)
523 unsigned int nr_columns
= 0, nr_rows
= 0;
528 name
= g_hash_table_lookup(hash
, "nm");
532 /* number of columns */
533 tmp
= g_hash_table_lookup(hash
, "col");
534 nr_columns
= (tmp
? atoi(tmp
) : 0);
537 tmp
= g_hash_table_lookup(hash
, "row");
538 nr_rows
= (tmp
? atoi(tmp
) : 0);
541 tmp
= g_hash_table_lookup(hash
, "mode");
542 mode
= (tmp
? atoi(tmp
) : 0);
545 tmp
= g_hash_table_lookup(hash
, "d");
549 coldata
= g_strsplit(tmp
, "~", 0); /* split into entries for each row & column */
551 if (g_strv_length(coldata
) != (nr_rows
* nr_columns
)) {
552 purple_debug_info(MXIT_PLUGIN_ID
, "Invalid table data: cols=%i rows=%i\n", nr_columns
, nr_rows
);
557 purple_debug_info(MXIT_PLUGIN_ID
, "Table %s from %s: [cols=%i rows=%i mode=%i]\n", name
, mx
->from
, nr_columns
, nr_rows
, mode
);
559 for (i
= 0; i
< nr_rows
; i
++) {
560 for (j
= 0; j
< nr_columns
; j
++) {
561 purple_debug_info(MXIT_PLUGIN_ID
, " Row %i Column %i = %s\n", i
, j
, coldata
[i
*nr_columns
+ j
]);
569 /*------------------------------------------------------------------------
570 * Process a received MXit Command message.
572 * @param mx The received message data object
573 * @param message The message text
574 * @return The length of the command
576 int mxit_parse_command(struct RXMsgData
* mx
, char* message
)
578 GHashTable
* hash
= NULL
;
582 /* ensure that this is really a command */
583 if ( ( message
[0] != ':' ) || ( message
[1] != ':' ) ) {
584 /* this is not a command */
589 end
= strstr(start
, ":");
591 /* end of a command found */
592 *end
= '\0'; /* terminate command string */
594 hash
= command_tokenize(start
); /* break into <key,value> pairs */
596 MXitCommandType type
= command_type(hash
);
599 case MXIT_CMD_CLEAR
:
600 command_clear(mx
->session
, mx
->from
, hash
);
602 case MXIT_CMD_REPLY
:
603 command_reply(mx
, hash
);
605 case MXIT_CMD_PLATREQ
:
606 command_platformreq(hash
, mx
->msg
);
608 case MXIT_CMD_IMAGE
:
609 command_image(mx
, hash
, mx
->msg
);
611 case MXIT_CMD_SCREENCONFIG
:
612 command_screenconfig(mx
->session
, mx
->from
, hash
);
614 case MXIT_CMD_SCREENINFO
:
615 command_screeninfo(mx
->session
, mx
->from
);
617 case MXIT_CMD_IMAGESTRIP
:
618 command_imagestrip(mx
->session
, mx
->from
, hash
);
620 case MXIT_CMD_TABLE
:
621 command_table(mx
, hash
);
624 /* command unknown, or not currently supported */
627 g_hash_table_destroy(hash
);
631 return end
- message
;