2 * Copyright (C) 2009 Richard Nelson <wabz@whatsbeef.net>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
21 #include <libsoup/soup.h>
23 #define PLUGIN_STATIC_NAME TinyURL
24 #define PREFS_BASE "/plugins/gnt/tinyurl"
25 #define PREF_LENGTH PREFS_BASE "/length"
26 #define PREF_URL PREFS_BASE "/url"
28 #include <conversation.h>
40 #include <gntplugin.h>
43 #include <gnttextview.h>
44 #include <gntwindow.h>
46 static int tag_num
= 0;
47 static SoupSession
*session
= NULL
;
48 static GHashTable
*tinyurl_cache
= NULL
;
53 PurpleConversation
*conv
;
58 static void process_urls(PurpleConversation
*conv
, GList
*urls
);
60 /* 3 functions from util.c */
81 badentity(const char *c
)
83 if (!g_ascii_strncasecmp(c
, "<", 4) ||
84 !g_ascii_strncasecmp(c
, ">", 4) ||
85 !g_ascii_strncasecmp(c
, """, 6)) {
91 static GList
*extract_urls(const char *text
)
93 const char *t
, *c
, *q
= NULL
;
96 gboolean inside_html
= FALSE
;
100 if (*c
== '(' && !inside_html
) {
107 } else if (!q
&& (*c
== '\"' || *c
== '\'')) {
113 } else if (*c
== '<') {
115 if (!g_ascii_strncasecmp(c
, "<A", 2)) {
126 } else if ((*c
=='h') && (!g_ascii_strncasecmp(c
, "http://", 7) ||
127 (!g_ascii_strncasecmp(c
, "https://", 8)))) {
130 if (badchar(*t
) || badentity(t
)) {
132 if ((!g_ascii_strncasecmp(c
, "http://", 7) && (t
- c
== 7)) ||
133 (!g_ascii_strncasecmp(c
, "https://", 8) && (t
- c
== 8))) {
137 if (*(t
) == ',' && (*(t
+ 1) != ' ')) {
144 if ((*(t
- 1) == ')' && (inside_paren
> 0))) {
148 url_buf
= g_strndup(c
, t
- c
);
149 if (!g_list_find_custom(ret
, url_buf
, (GCompareFunc
)strcmp
)) {
150 purple_debug_info("TinyURL", "Added URL %s\n", url_buf
);
151 ret
= g_list_append(ret
, url_buf
);
161 } else if (!g_ascii_strncasecmp(c
, "www.", 4) && (c
== text
|| badchar(c
[-1]) || badentity(c
-1))) {
165 if (badchar(*t
) || badentity(t
)) {
170 if (*(t
) == ',' && (*(t
+ 1) != ' ')) {
177 if ((*(t
- 1) == ')' && (inside_paren
> 0))) {
180 url_buf
= g_strndup(c
, t
- c
);
181 if (!g_list_find_custom(ret
, url_buf
, (GCompareFunc
)strcmp
)) {
182 purple_debug_info("TinyURL", "Added URL %s\n", url_buf
);
183 ret
= g_list_append(ret
, url_buf
);
194 if (*c
== ')' && !inside_html
) {
206 url_fetched(G_GNUC_UNUSED SoupSession
*session
, SoupMessage
*msg
,
209 CbInfo
*data
= (CbInfo
*)_data
;
210 PurpleConversation
*conv
= data
->conv
;
211 GList
*convs
= purple_conversations_get_all();
214 if (SOUP_STATUS_IS_SUCCESSFUL(msg
->status_code
)) {
215 url
= msg
->response_body
->data
;
216 g_hash_table_insert(tinyurl_cache
, data
->original_url
, g_strdup(url
));
218 url
= _("Error while querying TinyURL");
219 g_free(data
->original_url
);
222 /* ensure the conversation still exists */
223 if (g_list_find(convs
, conv
)) {
224 FinchConv
*fconv
= FINCH_CONV(conv
);
225 gchar
*str
= g_strdup_printf("[%d] %s", data
->num
, url
);
226 GntTextView
*tv
= GNT_TEXT_VIEW(fconv
->tv
);
227 gnt_text_view_tag_change(tv
, data
->tag
, str
, FALSE
);
235 purple_debug_info("TinyURL", "Conversation no longer exists... :(\n");
238 static gboolean
writing_msg(PurpleConversation
*conv
, PurpleMessage
*msg
, gpointer _unused
)
241 GList
*iter
, *urls
, *next
;
244 if (purple_message_get_flags(msg
) & (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_INVISIBLE
))
247 urls
= g_object_get_data(G_OBJECT(conv
), "TinyURLs");
248 g_list_free_full(urls
, g_free
);
249 urls
= extract_urls(purple_message_get_contents(msg
));
253 t
= g_string_new(g_strdup(purple_message_get_contents(msg
)));
254 for (iter
= urls
; iter
; iter
= next
) {
256 if (g_utf8_strlen((char *)iter
->data
, -1) >= purple_prefs_get_int(PREF_LENGTH
)) {
258 gchar
*j
, *s
, *str
, *orig
;
259 glong len
= g_utf8_strlen(iter
->data
, -1);
260 s
= g_strdup(t
->str
);
262 str
= g_strdup_printf("[%d]", ++c
);
263 while ((j
= strstr(s
, iter
->data
))) { /* replace all occurrences */
264 pos
= j
- orig
+ (x
++ * 3);
266 t
= g_string_insert(t
, pos
+ len
, str
);
267 if (*s
== '\0') break;
274 urls
= g_list_delete_link(urls
, iter
);
277 purple_message_set_contents(msg
, t
->str
);
278 g_string_free(t
, TRUE
);
280 g_object_set_data(G_OBJECT(conv
), "TinyURLs", urls
);
284 static void wrote_msg(PurpleConversation
*conv
, PurpleMessage
*pmsg
,
289 if (purple_message_get_flags(pmsg
) & PURPLE_MESSAGE_SEND
)
292 urls
= g_object_get_data(G_OBJECT(conv
), "TinyURLs");
296 process_urls(conv
, urls
);
297 g_object_set_data(G_OBJECT(conv
), "TinyURLs", NULL
);
302 process_urls(PurpleConversation
*conv
, GList
*urls
)
306 FinchConv
*fconv
= FINCH_CONV(conv
);
307 GntTextView
*tv
= GNT_TEXT_VIEW(fconv
->tv
);
309 for (iter
= urls
, c
= 1; iter
; iter
= iter
->next
, c
++) {
315 const gchar
*tiny_url
;
317 i
= gnt_text_view_get_lines_below(tv
);
319 original_url
= purple_unescape_html((char *)iter
->data
);
320 tiny_url
= g_hash_table_lookup(tinyurl_cache
, original_url
);
322 gchar
*str
= g_strdup_printf("\n[%d] %s", c
, tiny_url
);
324 g_free(original_url
);
325 gnt_text_view_append_text_with_flags(tv
, str
, GNT_TEXT_FLAG_DIM
);
327 gnt_text_view_scroll(tv
, 0);
331 cbdata
= g_new(CbInfo
, 1);
333 cbdata
->original_url
= original_url
;
334 cbdata
->tag
= g_strdup_printf("%s%d", "tiny_", tag_num
++);
336 if (g_ascii_strncasecmp(original_url
, "http://", 7) && g_ascii_strncasecmp(original_url
, "https://", 8)) {
337 url
= g_strdup_printf("%shttp%%3A%%2F%%2F%s", purple_prefs_get_string(PREF_URL
), purple_url_encode(original_url
));
339 url
= g_strdup_printf("%s%s", purple_prefs_get_string(PREF_URL
), purple_url_encode(original_url
));
341 msg
= soup_message_new("GET", url
);
342 soup_session_queue_message(session
, msg
, url_fetched
, cbdata
);
343 gnt_text_view_append_text_with_tag((tv
), _("\nFetching TinyURL..."),
344 GNT_TEXT_FLAG_DIM
, cbdata
->tag
);
346 gnt_text_view_scroll(tv
, 0);
354 free_conv_urls(PurpleConversation
*conv
)
356 GList
*urls
= g_object_get_data(G_OBJECT(conv
), "TinyURLs");
357 g_list_free_full(urls
, g_free
);
361 tinyurl_notify_tinyuri(GntWidget
*win
, const gchar
*url
)
364 GntWidget
*label
= g_object_get_data(G_OBJECT(win
), "info-widget");
366 message
= g_strdup_printf(_("TinyURL for above: %s"), url
);
367 gnt_label_set_text(GNT_LABEL(label
), message
);
372 cancel_notify_fetch(GntWidget
*win
, SoupMessage
*msg
)
374 soup_session_cancel_message(session
, msg
, SOUP_STATUS_CANCELLED
);
378 tinyurl_notify_fetch_cb(G_GNUC_UNUSED SoupSession
*session
, SoupMessage
*msg
,
381 GntWidget
*win
= _win
;
383 const gchar
*original_url
;
385 if (!SOUP_STATUS_IS_SUCCESSFUL(msg
->status_code
)) {
389 original_url
= g_object_get_data(G_OBJECT(win
), "gnttinyurl-original");
390 url
= msg
->response_body
->data
;
391 g_hash_table_insert(tinyurl_cache
,
392 g_strdup(original_url
), g_strdup(url
));
394 tinyurl_notify_tinyuri(win
, url
);
396 g_signal_handlers_disconnect_matched(G_OBJECT(win
), G_SIGNAL_MATCH_FUNC
, 0,
398 G_CALLBACK(cancel_notify_fetch
), NULL
);
402 tinyurl_notify_uri(const char *uri
)
404 char *fullurl
= NULL
;
407 const gchar
*tiny_url
;
409 /* XXX: The following expects that finch_notify_message gets called. This
410 * may not always happen, e.g. when another plugin sets its own
411 * notify_message. So tread carefully. */
412 win
= purple_notify_message(NULL
, PURPLE_NOTIFY_MSG_INFO
, _("URI"), uri
,
413 _("Please wait while TinyURL fetches a shorter URL ..."), NULL
, NULL
, NULL
);
414 if (!GNT_IS_WINDOW(win
) || !g_object_get_data(G_OBJECT(win
), "info-widget"))
417 tiny_url
= g_hash_table_lookup(tinyurl_cache
, uri
);
419 tinyurl_notify_tinyuri(win
, tiny_url
);
423 if (g_ascii_strncasecmp(uri
, "http://", 7) && g_ascii_strncasecmp(uri
, "https://", 8)) {
424 fullurl
= g_strdup_printf("%shttp%%3A%%2F%%2F%s",
425 purple_prefs_get_string(PREF_URL
), purple_url_encode(uri
));
427 fullurl
= g_strdup_printf("%s%s", purple_prefs_get_string(PREF_URL
),
428 purple_url_encode(uri
));
431 g_object_set_data_full(G_OBJECT(win
), "gnttinyurl-original", g_strdup(uri
), g_free
);
433 /* Store the SoupMessage and cancel that when the window is destroyed,
434 * so that the callback does not try to use a non-existent window.
436 msg
= soup_message_new("GET", fullurl
);
437 soup_session_queue_message(session
, msg
, tinyurl_notify_fetch_cb
, win
);
439 g_signal_connect(G_OBJECT(win
), "destroy", G_CALLBACK(cancel_notify_fetch
),
445 static PurplePluginPrefFrame
*
446 get_plugin_pref_frame(PurplePlugin
*plugin
) {
448 PurplePluginPrefFrame
*frame
;
449 PurplePluginPref
*pref
;
451 frame
= purple_plugin_pref_frame_new();
453 pref
= purple_plugin_pref_new_with_name(PREF_LENGTH
);
454 purple_plugin_pref_set_label(pref
, _("Only create TinyURL for URLs"
455 " of this length or greater"));
456 purple_plugin_pref_frame_add(frame
, pref
);
457 pref
= purple_plugin_pref_new_with_name(PREF_URL
);
458 purple_plugin_pref_set_label(pref
, _("TinyURL (or other) address prefix"));
459 purple_plugin_pref_frame_add(frame
, pref
);
464 static FinchPluginInfo
*
465 plugin_query(GError
**error
)
467 const gchar
* const authors
[] = {
468 "Richard Nelson <wabz@whatsbeef.net>",
472 return finch_plugin_info_new(
474 "name", N_("TinyURL"),
475 "version", DISPLAY_VERSION
,
476 "category", N_("Utility"),
477 "summary", N_("TinyURL plugin"),
478 "description", N_("When receiving a message with URL(s), "
479 "use TinyURL for easier copying"),
481 "website", PURPLE_WEBSITE
,
482 "abi-version", PURPLE_ABI_VERSION
,
483 "pref-frame-cb", get_plugin_pref_frame
,
489 plugin_load(PurplePlugin
*plugin
, GError
**error
)
491 PurpleNotifyUiOps
*ops
= purple_notify_get_ui_ops();
493 session
= soup_session_new();
495 purple_prefs_add_none(PREFS_BASE
);
496 purple_prefs_add_int(PREF_LENGTH
, 30);
497 purple_prefs_add_string(PREF_URL
, "http://tinyurl.com/api-create.php?url=");
499 g_object_set_data(G_OBJECT(plugin
), "notify-uri", ops
->notify_uri
);
500 ops
->notify_uri
= tinyurl_notify_uri
;
502 tinyurl_cache
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
505 purple_signal_connect(purple_conversations_get_handle(),
507 plugin
, PURPLE_CALLBACK(wrote_msg
), NULL
);
508 purple_signal_connect(purple_conversations_get_handle(),
510 plugin
, PURPLE_CALLBACK(wrote_msg
), NULL
);
511 purple_signal_connect(purple_conversations_get_handle(),
513 plugin
, PURPLE_CALLBACK(writing_msg
), NULL
);
514 purple_signal_connect(purple_conversations_get_handle(),
516 plugin
, PURPLE_CALLBACK(writing_msg
), NULL
);
517 purple_signal_connect(purple_conversations_get_handle(),
518 "deleting-conversation",
519 plugin
, PURPLE_CALLBACK(free_conv_urls
), NULL
);
525 plugin_unload(PurplePlugin
*plugin
, GError
**error
)
527 PurpleNotifyUiOps
*ops
= purple_notify_get_ui_ops();
528 if (ops
->notify_uri
== tinyurl_notify_uri
)
529 ops
->notify_uri
= g_object_get_data(G_OBJECT(plugin
), "notify-uri");
531 soup_session_abort(session
);
532 g_clear_object(&session
);
534 g_hash_table_destroy(tinyurl_cache
);
535 tinyurl_cache
= NULL
;
540 PURPLE_PLUGIN_INIT(PLUGIN_STATIC_NAME
, plugin_query
, plugin_load
, plugin_unload
);