# HG changeset patch # User Yoshiki Yazawa # Date 1227344478 -32400 # Node ID c2620a99622b2306a7aed0a1b8e24bf8630faf4a # Parent a37ae6c8fa669faed7890cc092e3712a83ec4d1e - divided the source file into several parts. diff -r a37ae6c8fa66 -r c2620a99622b Makefile.in --- a/Makefile.in Fri Nov 21 22:22:33 2008 +0900 +++ b/Makefile.in Sat Nov 22 18:01:18 2008 +0900 @@ -1,6 +1,7 @@ -OBJECTIVE = pidgin-twitter.so -SRC = pidgin-twitter.c -HDR = ${SRC:.c=.h} +TARGET = pidgin-twitter.so +SRCS = main.c util.c prefs.c icon.c twitter_api.c +OBJS = ${SRCS:.c=.o} +DEPEND = .deps PIDGIN_PLUGIN_DIR = @PIDGIN_PREFIX@/lib/pidgin PIDGIN_DATA_DIR = @PIDGIN_PREFIX@/share @@ -12,23 +13,27 @@ GLIB_LIBS = @GLIB_LIBS@ XML_LIBS = @XML_LIBS@ -LDFLAGS = $(GLIB_LIBS) $(XML_LIBS) +LDFLAGS = -fPIC -shared -Wall $(GLIB_LIBS) $(XML_LIBS) -all: $(OBJECTIVE) +all: $(TARGET) -$(OBJECTIVE): $(SRC) $(HDR) - gcc -o $@ $(SRC) $(CFLAGS) $(LDFLAGS) -g +$(TARGET): $(OBJS) + $(CC) -o $@ $^ $(LDFLAGS) -install: $(OBJECTIVE) - install -m 755 $(OBJECTIVE) $(PIDGIN_PLUGIN_DIR) +.c.o: + $(CC) -o $@ $< -c $(CFLAGS) + + +install: $(TARGET) + install -m 755 $(TARGET) $(PIDGIN_PLUGIN_DIR) install -d -m 755 $(PIDGIN_DATA_DIR)/pidgin-twitter install -m 644 prefs.ui $(PIDGIN_DATA_DIR)/pidgin-twitter clean: - rm -f $(OBJECTIVE) + rm -f $(TARGET) $(OBJS) $(DEPEND) distclean: clean @@ -42,3 +47,9 @@ release: maintainer-clean rm -f .hgtags rm -rf .hg + + +$(DEPEND): $(SRCS) + $(CC) -MM $(SRCS) > $@ + +include $(DEPEND) diff -r a37ae6c8fa66 -r c2620a99622b Makefile.mingw --- a/Makefile.mingw Fri Nov 21 22:22:33 2008 +0900 +++ b/Makefile.mingw Sat Nov 22 18:01:18 2008 +0900 @@ -13,7 +13,7 @@ STRIP = strip.exe TARGET = pidgin-twitter.dll -SRCS = pidgin-twitter.c +SRCS = main.c util.c prefs.c icon.c twitter_api.c OBJS = $(SRCS:%.c=%.o) prefs_ui.o @@ -55,7 +55,7 @@ strip: $(TARGET) $(STRIP) $^ -$(TARGET): $(OBJS) +$(TARGET): $(OBJS) $(CC) -shared $^ $(LDFLAGS) -o $@ .c.o: diff -r a37ae6c8fa66 -r c2620a99622b autogen.sh --- a/autogen.sh Fri Nov 21 22:22:33 2008 +0900 +++ b/autogen.sh Sat Nov 22 18:01:18 2008 +0900 @@ -1,6 +1,6 @@ #!/bin/sh -[ -f configure.in ] || { +[ -f configure.ac ] || { echo "autogen.sh: run this command only at the top of a pidgin-twitter source tree." exit 1 } diff -r a37ae6c8fa66 -r c2620a99622b configure.ac --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/configure.ac Sat Nov 22 18:01:18 2008 +0900 @@ -0,0 +1,74 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.59) +AC_INIT(pidgin-twitter, 0.9.0, yaz@honeyplanet.jp) +AC_CONFIG_SRCDIR([main.c]) + +# Checks for programs. +AC_PROG_CC + +# Checks for libraries. +PKG_CHECK_MODULES(GTK, [gtk+-2.0 >= 2.12.0], , [ + AC_MSG_RESULT(no) + AC_MSG_ERROR([ + +You must have gtk+-2.0 >= 2.12.0 development headers installed to build. +])]) + +PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.14.0], , [ + AC_MSG_RESULT(no) + AC_MSG_ERROR([ + +You must have glib >= 2.14.0 development headers installed to build. +])]) + +GLIB_CFLAGS=`pkg-config --cflags glib-2.0 2> /dev/null` +GLIB_LIBS=`pkg-config --libs glib-2.0 2> /dev/null` +GLIB_LIB_DIR=`pkg-config --variable=lib_dir glib-2.0 2> /dev/null` +AC_SUBST(GLIB_CFLAGS) +AC_SUBST(GLIB_LIBS) +AC_SUBST(GLIB_LIB_DIR) + +PKG_CHECK_MODULES(PIDGIN, [pidgin >= 2.4.0], , [ + AC_MSG_RESULT(no) + AC_MSG_ERROR([ + +You must have pidgin >= 2.4.0 development headers installed to build. +])]) +PIDGIN_CFLAGS=`pkg-config --cflags pidgin 2> /dev/null` +PIDGIN_LIBS=`pkg-config --libs pidgin 2> /dev/null` +if test x"$prefix" = x"NONE" ; then + PIDGIN_PREFIX=`pkg-config --variable=prefix pidgin 2> /dev/null` +else + PIDGIN_PREFIX=$prefix +fi +AC_SUBST(PIDGIN_CFLAGS) +AC_SUBST(PIDGIN_LIBS) +AC_SUBST(PIDGIN_PREFIX) + +PKG_CHECK_MODULES(XML, [libxml-2.0 >= 2.6.27], , [ + AC_MSG_RESULT(no) + AC_MSG_ERROR([ + +You must have libxml2 >= 2.6.27 installed to build. +])]) + +XML_CFLAGS=`pkg-config --cflags libxml-2.0 2> /dev/null` +XML_LIBS=`pkg-config --libs libxml-2.0 2> /dev/null` +XML_LIB_DIR=`pkg-config --variable=lib_dir libxml-2.0 2> /dev/null` +AC_SUBST(XML_CFLAGS) +AC_SUBST(XML_LIBS) +AC_SUBST(XML_LIB_DIR) + +# Checks for header files. +AC_HEADER_STDC +AC_CHECK_HEADERS([stdlib.h string.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST + +# Checks for library functions. +AC_CHECK_FUNCS([strstr]) + +AC_OUTPUT(Makefile) diff -r a37ae6c8fa66 -r c2620a99622b configure.in --- a/configure.in Fri Nov 21 22:22:33 2008 +0900 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -# -*- Autoconf -*- -# Process this file with autoconf to produce a configure script. - -AC_PREREQ(2.59) -AC_INIT(pidgin-twitter, 0.8.0, yaz@honeyplanet.jp) -AC_CONFIG_SRCDIR([pidgin-twitter.c]) - -# Checks for programs. -AC_PROG_CC - -# Checks for libraries. -PKG_CHECK_MODULES(GTK, [gtk+-2.0 >= 2.12.0], , [ - AC_MSG_RESULT(no) - AC_MSG_ERROR([ - -You must have gtk+-2.0 >= 2.12.0 development headers installed to build. -])]) - -PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.14.0], , [ - AC_MSG_RESULT(no) - AC_MSG_ERROR([ - -You must have glib >= 2.14.0 development headers installed to build. -])]) - -GLIB_CFLAGS=`pkg-config --cflags glib-2.0 2> /dev/null` -GLIB_LIBS=`pkg-config --libs glib-2.0 2> /dev/null` -GLIB_LIB_DIR=`pkg-config --variable=lib_dir glib-2.0 2> /dev/null` -AC_SUBST(GLIB_CFLAGS) -AC_SUBST(GLIB_LIBS) -AC_SUBST(GLIB_LIB_DIR) - -PKG_CHECK_MODULES(PIDGIN, [pidgin >= 2.4.0], , [ - AC_MSG_RESULT(no) - AC_MSG_ERROR([ - -You must have pidgin >= 2.4.0 development headers installed to build. -])]) -PIDGIN_CFLAGS=`pkg-config --cflags pidgin 2> /dev/null` -PIDGIN_LIBS=`pkg-config --libs pidgin 2> /dev/null` -if test x"$prefix" = x"NONE" ; then - PIDGIN_PREFIX=`pkg-config --variable=prefix pidgin 2> /dev/null` -else - PIDGIN_PREFIX=$prefix -fi -AC_SUBST(PIDGIN_CFLAGS) -AC_SUBST(PIDGIN_LIBS) -AC_SUBST(PIDGIN_PREFIX) - -PKG_CHECK_MODULES(XML, [libxml-2.0 >= 2.6.27], , [ - AC_MSG_RESULT(no) - AC_MSG_ERROR([ - -You must have libxml2 >= 2.6.27 installed to build. -])]) - -XML_CFLAGS=`pkg-config --cflags libxml-2.0 2> /dev/null` -XML_LIBS=`pkg-config --libs libxml-2.0 2> /dev/null` -XML_LIB_DIR=`pkg-config --variable=lib_dir libxml-2.0 2> /dev/null` -AC_SUBST(XML_CFLAGS) -AC_SUBST(XML_LIBS) -AC_SUBST(XML_LIB_DIR) - -# Checks for header files. -AC_HEADER_STDC -AC_CHECK_HEADERS([stdlib.h string.h]) - -# Checks for typedefs, structures, and compiler characteristics. -AC_C_CONST - -# Checks for library functions. -AC_CHECK_FUNCS([strstr]) - -AC_OUTPUT(Makefile) diff -r a37ae6c8fa66 -r c2620a99622b icon.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/icon.c Sat Nov 22 18:01:18 2008 +0900 @@ -0,0 +1,677 @@ +#include "pidgin-twitter.h" +#include "icon.h" + +extern GHashTable *icon_hash[]; +extern GRegex *regp[]; + +/* prototypes */ +static void insert_icon_at_mark(GtkTextMark *requested_mark, gpointer user_data); +static void insert_requested_icon(const gchar *user_name, gint service); +static void got_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message); +static GdkPixbuf *make_scaled_pixbuf(const gchar *url_text, gsize len); +static void got_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message); + +/* functions */ +static void +insert_icon_at_mark(GtkTextMark *requested_mark, gpointer user_data) +{ + got_icon_data *gotdata = (got_icon_data *)user_data; + + gchar *user_name = gotdata->user_name; + gint service = gotdata->service; + + GList *win_list; + GtkIMHtml *target_imhtml = NULL; + GtkTextBuffer *target_buffer = NULL; + GtkTextIter insertion_point; + icon_data *data = NULL; + GHashTable *hash = NULL; + + twitter_debug("called: service = %d\n", service); + + /* find the conversation that contains the mark */ + for(win_list = pidgin_conv_windows_get_list(); win_list; + win_list = win_list->next) { + PidginWindow *win = win_list->data; + GList *conv_list; + + for(conv_list = pidgin_conv_window_get_gtkconvs(win); conv_list; + conv_list = conv_list->next) { + PidginConversation *conv = conv_list->data; + PurpleConversation *purple_conv = conv->active_conv; + + gint service = get_service_type(purple_conv); + + if(service != unknown_service) { + GtkIMHtml *current_imhtml = GTK_IMHTML(conv->imhtml); + GtkTextBuffer *current_buffer = gtk_text_view_get_buffer( + GTK_TEXT_VIEW(current_imhtml)); + + if(current_buffer == gtk_text_mark_get_buffer(requested_mark)) { + target_imhtml = current_imhtml; + target_buffer = current_buffer; + break; + } + } + } + } + + if(!(target_imhtml && target_buffer)) { + return; + } + + /* insert icon to the mark */ + gtk_text_buffer_get_iter_at_mark(target_buffer, + &insertion_point, requested_mark); + + /* insert icon */ + switch(service) { + case twitter_service: + hash = icon_hash[twitter_service]; + break; + case wassr_service: + hash = icon_hash[wassr_service]; + break; + case identica_service: + hash = icon_hash[identica_service]; + break; + case jisko_service: + hash = icon_hash[jisko_service]; + break; + default: + twitter_debug("unknown service\n"); + } + + if(hash) + data = (icon_data *)g_hash_table_lookup(hash, user_name); + + + /* in this function, we put an icon for pending marks. we should + * not invalidate the icon here, otherwise it may result in + * thrashing. --yaz */ + + if(!data || !data->pixbuf) { + return; + } + + /* insert icon actually */ + if(purple_prefs_get_bool(OPT_SHOW_ICON)) { + gtk_text_buffer_insert_pixbuf(target_buffer, + &insertion_point, + data->pixbuf); + data->use_count++; + } + gtk_text_buffer_delete_mark(target_buffer, requested_mark); + requested_mark = NULL; +} + +static void +insert_requested_icon(const gchar *user_name, gint service) +{ + icon_data *data = NULL; + GList *mark_list = NULL; + GHashTable *hash = NULL; + + twitter_debug("called\n"); + + switch(service) { + case twitter_service: + hash = icon_hash[twitter_service]; + break; + case wassr_service: + hash = icon_hash[wassr_service]; + break; + case identica_service: + hash = icon_hash[identica_service]; + break; + case jisko_service: + hash = icon_hash[jisko_service]; + break; + default: + twitter_debug("unknown service\n"); + break; + } + + if(hash) + data = (icon_data *)g_hash_table_lookup(hash, user_name); + + if(!data) + return; + + mark_list = data->request_list; + + got_icon_data *gotdata = g_new0(got_icon_data, 1); + gotdata->user_name = g_strdup(user_name); + gotdata->service = service; + + twitter_debug("about to insert icon for pending requests\n"); + + if(mark_list) { + g_list_foreach(mark_list, (GFunc) insert_icon_at_mark, gotdata); + mark_list = g_list_remove_all(mark_list, NULL); + g_list_free(mark_list); + data->request_list = NULL; + } + + g_free(gotdata->user_name); + g_free(gotdata); +} + +/* this function will be called when profile page has been retrieved */ +static void +got_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, + const gchar *url_text, gsize len, const gchar *error_message) +{ + got_icon_data *gotdata = (got_icon_data *)user_data; + gchar *user_name = gotdata->user_name; + gint service = gotdata->service; + GMatchInfo *match_info = NULL; + icon_data *data = NULL; + gchar *url = NULL; + gint regp_id = -1; + + if(service == twitter_service) { + data = (icon_data *)g_hash_table_lookup( + icon_hash[twitter_service], user_name); + regp_id = IMAGE_TWITTER; + } + else if(service == wassr_service) { + data = (icon_data *)g_hash_table_lookup( + icon_hash[wassr_service], user_name); + regp_id = IMAGE_WASSR; + } + else if(service == identica_service) { + data = (icon_data *)g_hash_table_lookup( + icon_hash[identica_service], user_name); + regp_id = IMAGE_IDENTICA; + } + else if(service == jisko_service) { + data = (icon_data *)g_hash_table_lookup( + icon_hash[jisko_service], user_name); + regp_id = IMAGE_JISKO; + } + + if(!url_text) { + if(data) { + data->requested = FALSE; + data->fetch_data = NULL; + } + g_free(gotdata->user_name); + g_free(gotdata); + return; + } + + /* setup image url */ /* xxx need simplify --yaz */ + g_regex_match(regp[regp_id], url_text, 0, &match_info); + if(!g_match_info_matches(match_info)) { + g_match_info_free(match_info); + + if(service == twitter_service) { + twitter_debug("fall back to twitter default icon\n"); + url = g_strdup(TWITTER_DEFAULT_ICON_URL); + } + else if(service == jisko_service) { + twitter_debug("fall back to jisko default icon\n"); + url = g_strdup(JISKO_DEFAULT_ICON_URL); + } + else { + twitter_debug("no image url found\n"); + if(data) { + data->requested = FALSE; + data->fetch_data = NULL; + } + g_free(gotdata->user_name); + g_free(gotdata); + return; + } + } + else { + url = g_match_info_fetch(match_info, 1); + g_match_info_free(match_info); + } + + /* find out basename */ + gchar *slash = strrchr(url, '/'); + *slash = '\0'; + + gchar *lower = g_ascii_strdown(slash+1, -1); + + if(strstr(lower, ".png")) + data->img_type = "png"; + else if(strstr(lower, ".gif")) + data->img_type = "gif"; + else if(strstr(lower, ".jpg") || strstr(lower, ".jpeg")) + data->img_type = "jpg"; + + g_free(lower); + + gchar *tmp; + /* url encode basename. twitter needs this. */ + if(service == twitter_service) + tmp = g_strdup_printf("%s/%s", url, + purple_url_encode(slash+1)); + else if(service == wassr_service) { + gchar *tmp0 = NULL; + tmp0 = g_regex_replace(regp[SIZE_128_WASSR], slash+1, + -1, 0, ".64.", 0, NULL); + tmp = g_strdup_printf("http://wassr.jp%s/%s", url, + tmp0 ? tmp0 : slash+1); + g_free(tmp0); + } + else { + tmp = g_strdup_printf("%s/%s", url, slash+1); + } + + g_free(url); + url = tmp; + + /* if requesting icon url is the same as old, return. */ + if(url && data->icon_url && !strcmp(data->icon_url, url)) { + twitter_debug("old url = %s new url = %s\n", data->icon_url, url); + data->requested = FALSE; + data->fetch_data = NULL; + g_free(url); + return; + } + + if(data && data->pixbuf) { + gdk_pixbuf_unref(data->pixbuf); + data->pixbuf = NULL; + } + + g_free(data->icon_url); + data->icon_url = g_strdup(url); + + data->use_count = 0; + data->mtime = time(NULL); /* xxx is there a better way? */ + + twitter_debug("requested url=%s\n", url); + + /* request fetch image */ + if(url) { + /* reuse gotdata. just pass given one */ + /* gotdata will be released in got_icon_cb */ + data->fetch_data = purple_util_fetch_url(url, + TRUE, NULL, TRUE, + got_icon_cb, gotdata); + twitter_debug("request %s's icon\n", user_name); + g_free(url); + } +} + +static GdkPixbuf * +make_scaled_pixbuf(const gchar *url_text, gsize len) +{ + /* make pixbuf */ + GdkPixbufLoader *loader; + GdkPixbuf *src = NULL, *dest = NULL; + gint size; + + g_return_val_if_fail(url_text != NULL, NULL); + g_return_val_if_fail(len > 0, NULL); + + loader = gdk_pixbuf_loader_new(); + gdk_pixbuf_loader_write(loader, (guchar *)url_text, len, NULL); + gdk_pixbuf_loader_close(loader, NULL); + + src = gdk_pixbuf_loader_get_pixbuf(loader); + if(!src) + return NULL; + + size = purple_prefs_get_int(OPT_ICON_SIZE); + if(size == 0) + size = DEFAULT_ICON_SIZE; + + dest = gdk_pixbuf_scale_simple(src, size, size, GDK_INTERP_HYPER); + gdk_pixbuf_unref(src); + + return dest; +} + +static gchar *ext_list[] = { + "png", + "gif", + "jpg", + NULL +}; + +/* this function will be called when requested icon has been retrieved */ +static void +got_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, + const gchar *url_text, gsize len, const gchar *error_message) +{ + got_icon_data *gotdata = (got_icon_data *)user_data; + gchar *user_name = gotdata->user_name; + gint service = gotdata->service; + + icon_data *data = NULL; + GHashTable *hash = NULL; + GdkPixbuf *pixbuf = NULL; + const gchar *dirname = NULL; + + twitter_debug("called: service = %d\n", service); + + switch(service) { + case twitter_service: + hash = icon_hash[twitter_service]; + break; + case wassr_service: + hash = icon_hash[wassr_service]; + break; + case identica_service: + hash = icon_hash[identica_service]; + break; + case jisko_service: + hash = icon_hash[jisko_service]; + break; + default: + twitter_debug("unknown service\n"); + } + + if(hash) + data = (icon_data *)g_hash_table_lookup(hash, user_name); + + /* return if download failed */ + if(!url_text) { + twitter_debug("downloading %s's icon failed : %s\n", + user_name, error_message); + if(data) + data->requested = FALSE; + + goto fin_got_icon_cb; + } + + if(data) { + /* remove download request */ + data->requested = FALSE; + data->fetch_data = NULL; + + /* return if user's icon had been downloaded */ + if(data->pixbuf) { + twitter_debug("%s's icon has already been downloaded\n", + user_name); + + goto fin_got_icon_cb; + } + } + + pixbuf = make_scaled_pixbuf(url_text, len); + + if(!pixbuf) + goto fin_got_icon_cb; + + + if(!data) { + twitter_debug("allocate icon_data (shouldn't be called)\n"); + data = g_new0(icon_data, 1); + } + + data->pixbuf = pixbuf; + + twitter_debug("new icon pixbuf = %p size = %d\n", + pixbuf, + gdk_pixbuf_get_rowstride(pixbuf) * + gdk_pixbuf_get_height(pixbuf)); + + if(hash) + g_hash_table_insert(hash, g_strdup(user_name), data); + + dirname = purple_prefs_get_string(OPT_ICON_DIR); + + /* store retrieved image to a file in icon dir */ + if(ensure_path_exists(dirname)) { + gchar *filename = NULL; + gchar *path = NULL; + const gchar *suffix = NULL; + gchar **extp; + + switch(service) { + case twitter_service: + suffix = "twitter"; + break; + case wassr_service: + suffix = "wassr"; + break; + case identica_service: + suffix = "identica"; + break; + case jisko_service: + suffix = "jisko"; + break; + default: + twitter_debug("unknown service\n"); + break; + } + + /* remove old file first */ + for(extp = ext_list; *extp; extp++) { + filename = g_strdup_printf("%s_%s.%s", + user_name, suffix, *extp); + path = g_build_filename(dirname, filename, NULL); + g_remove(path); + + g_free(filename); + g_free(path); + } + + /* setup path */ + filename = g_strdup_printf("%s_%s.%s", + user_name, suffix, data->img_type); + + path = g_build_filename(dirname, filename, NULL); + g_free(filename); filename = NULL; + + g_file_set_contents(path, url_text, len, NULL); + g_free(path); path = NULL; + + data->mtime = time(NULL); + } + + twitter_debug("Downloading %s's icon has been complete.\n", + user_name); + + /* Insert the icon to messages that had been received. */ + insert_requested_icon(user_name, service); + +fin_got_icon_cb: + g_free(gotdata->user_name); + g_free(gotdata); +} + +void +request_icon(const char *user_name, gint service, gboolean renew) +{ + gchar *url = NULL; + + /* look local icon cache for the requested icon */ + gchar *path = NULL; + icon_data *data = NULL; + GHashTable *hash = NULL; + const gchar *suffix = NULL; + + switch(service) { + case twitter_service: + hash = icon_hash[twitter_service]; + suffix = "twitter"; + break; + case wassr_service: + hash = icon_hash[wassr_service]; + suffix = "wassr"; + break; + case identica_service: + suffix = "identica"; + hash = icon_hash[identica_service]; + break; + case jisko_service: + suffix = "jisko"; + hash = icon_hash[jisko_service]; + break; + default: + twitter_debug("unknown service\n"); + break; + } + + if(!hash) + return; + + /* since this function is called after mark_icon_for_user(), data + * must exist here. */ + data = (icon_data *)g_hash_table_lookup(hash, user_name); + + /* if img has been registerd, just return */ + if(data && data->pixbuf && !renew) + return; + + /* check if saved file exists */ + if(suffix && !renew) { + gchar *filename = NULL; + gchar **extp; + + for(extp = ext_list; *extp; extp++) { + filename = g_strdup_printf("%s_%s.%s", user_name, suffix, *extp); + path = g_build_filename(purple_prefs_get_string(OPT_ICON_DIR), + filename, NULL); + g_free(filename); + + twitter_debug("path = %s\n", path); + + /* build image from file, if file exists */ + if(g_file_test(path, G_FILE_TEST_EXISTS)) { + gchar *imgdata = NULL; + size_t len; + GError *err = NULL; + GdkPixbuf *pixbuf = NULL; + struct stat buf; + + if (!g_file_get_contents(path, &imgdata, &len, &err)) { + twitter_debug("Error reading %s: %s\n", + path, err->message); + g_error_free(err); + } + + if(stat(path, &buf)) + data->mtime = buf.st_mtime; + + pixbuf = make_scaled_pixbuf(imgdata, len); + g_free(imgdata); + + if(pixbuf) { + data->pixbuf = pixbuf; + + twitter_debug("new icon pixbuf = %p size = %d\n", + pixbuf, + gdk_pixbuf_get_rowstride(pixbuf) * + gdk_pixbuf_get_height(pixbuf)); + + data->img_type = *extp; + + twitter_debug("icon data has been loaded from file\n"); + insert_requested_icon(user_name, service); + } + + g_free(path); + return; + } + + g_free(path); + + } /* for */ + } /* suffix */ + + /* Return if user's icon has been requested already. */ + if(data->requested) + return; + else + data->requested = TRUE; + + /* Create the URL for an user's icon. */ + switch(service) { + case twitter_service: + url = g_strdup_printf("http://twitter.com/%s", user_name); + break; + case wassr_service: + url = g_strdup_printf("http://wassr.jp/user/%s", user_name); + break; + case identica_service: + url = g_strdup_printf("http://identi.ca/%s", user_name); + break; + case jisko_service: + url = g_strdup_printf("http://jisko.net/%s", user_name); + break; + default: + twitter_debug("unknown service\n"); + break; + } + + if(url) { + got_icon_data *gotdata = g_new0(got_icon_data, 1); + gotdata->user_name = g_strdup(user_name); + gotdata->service = service; + + /* gotdata will be released in got_icon_cb */ + if(service == twitter_service || + service == wassr_service || + service == identica_service || + service == jisko_service) { + data->fetch_data = purple_util_fetch_url(url, TRUE, NULL, TRUE, + got_page_cb, gotdata); + } + else { + data->fetch_data = purple_util_fetch_url(url, TRUE, NULL, TRUE, + got_icon_cb, gotdata); + } + g_free(url); url = NULL; + + twitter_debug("request %s's icon\n", user_name); + } +} + +void +mark_icon_for_user(GtkTextMark *mark, const gchar *user_name, gint service) +{ + icon_data *data = NULL; + GHashTable *hash = NULL; + + twitter_debug("called\n"); + + switch(service) { + case twitter_service: + hash = icon_hash[twitter_service]; + break; + case wassr_service: + hash = icon_hash[wassr_service]; + break; + case identica_service: + hash = icon_hash[identica_service]; + break; + case jisko_service: + hash = icon_hash[jisko_service]; + break; + default: + twitter_debug("unknown service\n"); + break; + } + + if(hash) + data = (icon_data *)g_hash_table_lookup(hash, user_name); + + /* proper place to allocate icon_data */ + if(!data) { + data = g_new0(icon_data, 1); + g_hash_table_insert(hash, g_strdup(user_name), data); + } + + data->request_list = g_list_prepend(data->request_list, mark); +} + +void +invalidate_icon_data_func(gpointer key, gpointer value, gpointer user_data) +{ + icon_data *data = (icon_data *)value; + + g_return_if_fail(data != NULL); + + g_object_unref(data->pixbuf); + data->pixbuf = NULL; +} + diff -r a37ae6c8fa66 -r c2620a99622b icon.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/icon.h Sat Nov 22 18:01:18 2008 +0900 @@ -0,0 +1,8 @@ +#ifndef _PIDGIN_TWITTER_ICON_H_ +#define _PIDGIN_TWITTER_ICON_H_ + +void invalidate_icon_data_func(gpointer key, gpointer value, gpointer user_data); +void request_icon(const char *user_name, gint service, gboolean renew); +void mark_icon_for_user(GtkTextMark *mark, const gchar *user_name, gint service); + +#endif diff -r a37ae6c8fa66 -r c2620a99622b main.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.c Sat Nov 22 18:01:18 2008 +0900 @@ -0,0 +1,1489 @@ +/* + * Pidgin-Twitter plugin. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ +#define PURPLE_PLUGINS 1 + +#include "pidgin-twitter.h" +#include "main.h" + +/***********/ +/* globals */ +/***********/ +GRegex *regp[NUM_REGPS]; +static gboolean suppress_oops = FALSE; +GHashTable *icon_hash[NUM_SERVICES]; +static GHashTable *conv_hash = NULL; +static GList *wassr_parrot_list = NULL; +static GList *identica_parrot_list = NULL; +#ifdef _WIN32 +static gboolean blink_state = FALSE; +static gboolean blink_modified = FALSE; +#endif + +source_t source; + +#ifndef _WIN32 +extern gchar *sanitize_utf(const gchar *msg, gsize len, gsize *newlen) __attribute__ ((weak)); +#endif + +/* prototypes */ +static gboolean is_twitter_conv(PurpleConversation *conv); +static gboolean is_wassr_account(PurpleAccount *account, const char *name); +static gboolean is_wassr_conv(PurpleConversation *conv); +static gboolean is_identica_account(PurpleAccount *account, const char *name); +static gboolean is_identica_conv(PurpleConversation *conv); +static gboolean is_jisko_account(PurpleAccount *account, const char *name); +static gboolean is_jisko_conv(PurpleConversation *conv); +static void cleanup_hash_entry_func(gpointer key, gpointer value, gpointer user_data); + +static void init_plugin(PurplePlugin *plugin); +static gboolean load_plugin(PurplePlugin *plugin); +static gboolean unload_plugin(PurplePlugin *plugin); +static gboolean sending_im_cb(PurpleAccount *account, char *recipient, char **buffer, void *data); +static gboolean eval(const GMatchInfo *match_info, GString *result, gpointer user_data); +static void translate(gchar **str, gint which, gint service); +static void playsound(gchar **str, gint which); +static gboolean writing_im_cb(PurpleAccount *account, char *sender, char **buffer, PurpleConversation *conv, int flags, void *data); +static void insert_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *position, gchar *new_text, gint new_text_length, gpointer user_data); +static void delete_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *start_pos, GtkTextIter *end_pos, gpointer user_data); +static void detach_from_conv(PurpleConversation *conv, gpointer null); +static void delete_requested_icon_marks(PidginConversation *gtkconv, GHashTable *table); +static void attach_to_conv(PurpleConversation *conv, gpointer null); + + +static void conv_created_cb(PurpleConversation *conv, gpointer null); +static void deleting_conv_cb(PurpleConversation *conv); +static gboolean receiving_im_cb(PurpleAccount *account, char **sender, char **buffer, PurpleConversation *conv, PurpleMessageFlags *flags, void *data); + +static void remove_marks_func(gpointer key, gpointer value, gpointer user_data); +static void cancel_fetch_func(gpointer key, gpointer value, gpointer user_data); + +static gboolean displaying_im_cb(PurpleAccount *account, const char *who, char **message, PurpleConversation *conv, PurpleMessageFlags flags, void *data); +static void displayed_im_cb(PurpleAccount *account, const char *who, char *message, PurpleConversation *conv, PurpleMessageFlags flags); + + + +/*************/ +/* functions */ +/*************/ + + +/***********************/ +/* intrinsic functions */ +/***********************/ +static gboolean +sending_im_cb(PurpleAccount *account, char *recipient, char **buffer, + void *data) +{ + int utflen, bytes; + gboolean twitter_ac = FALSE, wassr_ac = FALSE, identica_ac = FALSE; + twitter_debug("called\n"); + + twitter_ac = is_twitter_account(account, recipient); + wassr_ac = is_wassr_account(account, recipient); + identica_ac = is_identica_account(account, recipient); + + /* strip all markups */ + if(twitter_ac || wassr_ac || identica_ac) { + gchar *tmp, *plain; + gsize dummy; + + tmp = strip_html_markup(*buffer); + +#ifndef _WIN32 + if(sanitize_utf) { + plain = sanitize_utf(tmp, -1, &dummy); + g_free(tmp); + } + else +#endif + plain = tmp; + + if(wassr_ac) { + /* store sending message to address parrot problem */ + wassr_parrot_list = + g_list_prepend(wassr_parrot_list, g_strdup(plain)); + twitter_debug("wassr parrot pushed:%s\n", plain); + } + else if(identica_ac) { + /* store sending message to address parrot problem */ + identica_parrot_list = + g_list_prepend(identica_parrot_list, g_strdup(plain)); + twitter_debug("identica parrot pushed:%s\n", plain); + } + + g_free(*buffer); + *buffer = g_markup_escape_text(plain, -1); + g_free(plain); + } + + /* return here if the message is not to twitter */ + if(!twitter_ac) + return FALSE; + + /* escape pseudo command */ + if(twitter_ac && + purple_prefs_get_bool(OPT_ESCAPE_PSEUDO)) { + escape(buffer); + } + + /* update status with Twitter API instead of IM protocol */ + if (purple_prefs_get_bool(OPT_API_BASE_POST)) { + if (buffer && *buffer) { + post_status_with_api(account, buffer); + (*buffer)[0] = '\0'; + } + return FALSE; + } + + /* try to suppress oops message */ + utflen = g_utf8_strlen(*buffer, -1); + bytes = strlen(*buffer); + twitter_debug("utflen = %d bytes = %d\n", utflen, bytes); + if(bytes > 140 && utflen <= 140) + suppress_oops = TRUE; + + return FALSE; +} + +static gboolean +eval(const GMatchInfo *match_info, GString *result, gpointer user_data) +{ + eval_data *data = (eval_data *)user_data; + gint which = data->which; + gint service = data->service; + gchar sub[SUBST_BUF_SIZE]; + + twitter_debug("which = %d service = %d\n", which, service); + + if(which == RECIPIENT) { + gchar *match1 = g_match_info_fetch(match_info, 1); /* preceding \s */ + gchar *match2 = g_match_info_fetch(match_info, 2); /* recipient */ + const gchar *format = NULL; + + switch(service) { + case twitter_service: + format = RECIPIENT_FORMAT_TWITTER; + break; + case wassr_service: + format = RECIPIENT_FORMAT_WASSR; + break; + case identica_service: + format = RECIPIENT_FORMAT_IDENTICA; + break; + case jisko_service: + format = RECIPIENT_FORMAT_JISKO; + break; + default: + twitter_debug("unknown service\n"); + break; + } + g_snprintf(sub, SUBST_BUF_SIZE, format, match1 ? match1: "", match2, match2); + g_free(match1); + g_free(match2); + } + else if(which == SENDER) { + gchar *match1 = g_match_info_fetch(match_info, 1); /*preceding CR|LF*/ + gchar *match2 = g_match_info_fetch(match_info, 2); /* sender */ + const gchar *format = NULL; + + switch(service) { + case twitter_service: + format = SENDER_FORMAT_TWITTER; + break; + case wassr_service: + format = SENDER_FORMAT_WASSR; + break; + case identica_service: + format = SENDER_FORMAT_IDENTICA; + break; + case jisko_service: + format = SENDER_FORMAT_JISKO; + break; + default: + twitter_debug("unknown service\n"); + break; + } + + g_snprintf(sub, SUBST_BUF_SIZE, format, match1 ? match1: "", match2, match2); + + g_free(match1); + g_free(match2); + } + else if(which == CHANNEL_WASSR && service == wassr_service) { + gchar *match1 = g_match_info_fetch(match_info, 1); /*before channel*/ + gchar *match2 = g_match_info_fetch(match_info, 2); /* channel */ + const gchar *format = CHANNEL_FORMAT_WASSR; + + g_snprintf(sub, SUBST_BUF_SIZE, format, match1 ? match1: "", match2, match2); + + g_free(match1); + g_free(match2); + } + else if(which == TAG_IDENTICA && service == identica_service) { + gchar *match = g_match_info_fetch(match_info, 1); + gchar *link = g_ascii_strdown(match, -1); + purple_str_strip_char(link, '-'); + purple_str_strip_char(link, '_'); + const gchar *format = TAG_FORMAT_IDENTICA; + g_snprintf(sub, SUBST_BUF_SIZE, format, link, match); + g_free(match); + g_free(link); + } + else if(which == EXCESS_LF) { + g_snprintf(sub, SUBST_BUF_SIZE, "%s", "\n"); + } + + g_string_append(result, sub); + twitter_debug("sub = %s\n", sub); + + return FALSE; +} + +static void +translate(gchar **str, gint regp_id, gint service) +{ + gchar *newstr; + eval_data *data = g_new0(eval_data, 1); + + data->which = regp_id; + data->service = service; + + newstr = g_regex_replace_eval(regp[regp_id], /* compiled regex */ + *str, /* subject string */ + -1, /* length of the subject string */ + 0, /* start position */ + 0, /* match options */ + eval, /* function to be called for each match */ + data, /* user data */ + NULL); /* error handler */ + + g_free(data); data = NULL; + + twitter_debug("which = %d *str = %s newstr = %s\n", regp_id, *str, newstr); + + g_free(*str); + *str = newstr; +} + +static void +playsound(gchar **str, gint which) +{ + GMatchInfo *match_info; + const gchar *list = NULL; + gchar **candidates = NULL, **candidate = NULL; + + list = purple_prefs_get_string(which ? OPT_USERLIST_SENDER : + OPT_USERLIST_RECIPIENT); + g_return_if_fail(list != NULL); + if(strstr(list, DEFAULT_LIST)) + return; + + candidates = g_strsplit_set(list, " ,:;", 0); + g_return_if_fail(candidates != NULL); + + g_regex_match(regp[which], *str, 0, &match_info); + while(g_match_info_matches(match_info)) { + gchar *user = NULL; + if(which == RECIPIENT) + user = g_match_info_fetch(match_info, 2); + else if(which == SENDER) + user = g_match_info_fetch(match_info, 2); + twitter_debug("user = %s\n", user); + + for(candidate = candidates; *candidate; candidate++) { + if(!strcmp(*candidate, "")) + continue; + twitter_debug("candidate = %s\n", *candidate); + if(!strcmp(user, *candidate)) { + twitter_debug("match. play sound\n"); + purple_sound_play_event(purple_prefs_get_int + (which ? OPT_SOUNDID_SENDER : + OPT_SOUNDID_RECIPIENT), NULL); + break; + } + } + g_free(user); + g_match_info_next(match_info, NULL); + } + g_strfreev(candidates); + g_match_info_free(match_info); +} + +static gboolean +writing_im_cb(PurpleAccount *account, char *sender, char **buffer, + PurpleConversation *conv, int flags, void *data) +{ + twitter_debug("called\n"); + + gint service = get_service_type(conv); + + /* check if the conversation is between twitter */ + if(service == unknown_service) + return FALSE; + + /* add screen_name if the current message is posted by myself */ + if (flags & PURPLE_MESSAGE_SEND) { + gchar *m = NULL; + const char *screen_name = NULL; + + switch(service) { + case twitter_service: + screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER); + break; + case wassr_service: + screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_WASSR); + break; + case identica_service: + screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_IDENTICA); + break; + case jisko_service: + screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_JISKO); + break; + } + + if (screen_name) { + m = g_strdup_printf("%s: %s", screen_name, *buffer); + g_free(*buffer); + *buffer = m; + } + } + + /* strip all markups */ + strip_markup(buffer, TRUE); + + /* playsound */ + if(purple_prefs_get_bool(OPT_PLAYSOUND_SENDER)) { + playsound(buffer, SENDER); + } + if(purple_prefs_get_bool(OPT_PLAYSOUND_RECIPIENT)) { + playsound(buffer, RECIPIENT); + } + + /* translate */ + if(purple_prefs_get_bool(OPT_TRANSLATE_SENDER)) { + translate(buffer, SENDER, service); + } + if(purple_prefs_get_bool(OPT_TRANSLATE_RECIPIENT)) { + translate(buffer, RECIPIENT, service); + } + if(service == wassr_service && + purple_prefs_get_bool(OPT_TRANSLATE_CHANNEL)) { + translate(buffer, CHANNEL_WASSR, service); + } + if(service == identica_service && + purple_prefs_get_bool(OPT_TRANSLATE_CHANNEL)) { + translate(buffer, TAG_IDENTICA, service); + } + + /* escape pseudo command (to show the same result as sending message) */ + if(service == twitter_service && + purple_prefs_get_bool(OPT_ESCAPE_PSEUDO)) { + escape(buffer); + } + + if(purple_prefs_get_bool(OPT_STRIP_EXCESS_LF)) { + translate(buffer, EXCESS_LF, service); + } + + return FALSE; +} + +static void +insert_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *position, + gchar *new_text, gint new_text_length, gpointer user_data) +{ + PurpleConversation *conv = (PurpleConversation *)user_data; + PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); + + GtkWidget *box, *counter = NULL; + gchar *markup = NULL; + gint service = get_service_type(conv); + guint count; + + g_return_if_fail(gtkconv != NULL); + + switch(service) { + case twitter_service: + case identica_service: + case jisko_service: + count = gtk_text_buffer_get_char_count(textbuffer) + + (unsigned int)g_utf8_strlen(new_text, -1); + markup = g_markup_printf_escaped("%u", + count <= 140 ? "black" : "red", count); + break; + case wassr_service: + count = gtk_text_buffer_get_char_count(textbuffer) + + (unsigned int)g_utf8_strlen(new_text, -1); + markup = g_markup_printf_escaped("%u", + count <= 255 ? "black" : "red", count); + break; + default: + twitter_debug("unknown service\n"); + break; + } + + box = gtkconv->toolbar; + counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter"); + if(counter) + gtk_label_set_markup(GTK_LABEL(counter), markup); + + g_free(markup); +} + +static void +delete_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *start_pos, + GtkTextIter *end_pos, gpointer user_data) +{ + PurpleConversation *conv = (PurpleConversation *)user_data; + PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); + GtkWidget *box, *counter = NULL; + gchar *markup = NULL; + gint service = get_service_type(conv); + guint count = 0; + + g_return_if_fail(gtkconv != NULL); + + switch(service) { + case twitter_service: + case identica_service: + case jisko_service: + count= gtk_text_buffer_get_char_count(textbuffer) - + (gtk_text_iter_get_offset(end_pos) - + gtk_text_iter_get_offset(start_pos)); + markup = g_markup_printf_escaped("%u", + count <= 140 ? "black" : "red", count); + break; + case wassr_service: + count= gtk_text_buffer_get_char_count(textbuffer) - + (gtk_text_iter_get_offset(end_pos) - + gtk_text_iter_get_offset(start_pos)); + markup = g_markup_printf_escaped("%u", + count <= 255 ? "black" : "red", count); + break; + default: + twitter_debug("unknown service\n"); + break; + } + + box = gtkconv->toolbar; + counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter"); + if(counter) + gtk_label_set_markup(GTK_LABEL(counter), markup); + + g_free(markup); +} + +void +detach_from_window(void) +{ + GList *list; + + /* find twitter conv window out and detach from that */ + for(list = pidgin_conv_windows_get_list(); list; list = list->next) { + PidginWindow *win = list->data; + PurpleConversation *conv = + pidgin_conv_window_get_active_conversation(win); + gint service = get_service_type(conv); + switch(service) { + case twitter_service: + case wassr_service: + case identica_service: + case jisko_service: + detach_from_conv(conv, NULL); + break; + default: + twitter_debug("unknown service\n"); + break; + } + } +} + +static void +detach_from_conv(PurpleConversation *conv, gpointer null) +{ + PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); + GtkWidget *box, *counter = NULL, *sep = NULL; + + g_signal_handlers_disconnect_by_func(G_OBJECT(gtkconv->entry_buffer), + (GFunc) insert_text_cb, conv); + g_signal_handlers_disconnect_by_func(G_OBJECT(gtkconv->entry_buffer), + (GFunc) delete_text_cb, conv); + + box = gtkconv->toolbar; + + /* remove counter */ + counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter"); + if(counter) { + gtk_container_remove(GTK_CONTAINER(box), counter); + g_object_unref(counter); + g_object_set_data(G_OBJECT(box), PLUGIN_ID "-counter", NULL); + } + + /* remove separator */ + sep = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-sep"); + if(sep) { + gtk_container_remove(GTK_CONTAINER(box), sep); + g_object_unref(sep); + g_object_set_data(G_OBJECT(box), PLUGIN_ID "-sep", NULL); + } + + gtk_widget_queue_draw(pidgin_conv_get_window(gtkconv)->window); +} + +static void +remove_marks_func(gpointer key, gpointer value, gpointer user_data) +{ + icon_data *data = (icon_data *)value; + GtkTextBuffer *text_buffer = (GtkTextBuffer *)user_data; + GList *mark_list = NULL; + GList *current; + + if(!data) + return; + + if(data->request_list) + mark_list = data->request_list; + + /* remove the marks in its GtkTextBuffers */ + current = g_list_first(mark_list); + while(current) { + GtkTextMark *current_mark = current->data; + GtkTextBuffer *current_text_buffer = + gtk_text_mark_get_buffer(current_mark); + GList *next; + + next = g_list_next(current); + + if(!current_text_buffer) + continue; + + if(text_buffer) { + if(current_text_buffer == text_buffer) { + /* the mark will be freed in this function */ + gtk_text_buffer_delete_mark(current_text_buffer, + current_mark); + current->data = NULL; + mark_list = g_list_delete_link(mark_list, current); + } + } + else { + gtk_text_buffer_delete_mark(current_text_buffer, current_mark); + current->data = NULL; + mark_list = g_list_delete_link(mark_list, current); + } + + current = next; + } + + data->request_list = mark_list; +} + +static void +delete_requested_icon_marks(PidginConversation *conv, GHashTable *table) { + GtkTextBuffer *text_buffer = gtk_text_view_get_buffer( + GTK_TEXT_VIEW(conv->imhtml)); + + g_hash_table_foreach(table, + (GHFunc)remove_marks_func, + (gpointer)text_buffer); +} + +void +attach_to_window(void) +{ + GList *list; + + twitter_debug("called\n"); + + /* find twitter conv window out and attach to that */ + for(list = pidgin_conv_windows_get_list(); list; list = list->next) { + PidginWindow *win = list->data; + PurpleConversation *conv = + pidgin_conv_window_get_active_conversation(win); + gint service = get_service_type(conv); + /* only attach to twitter conversation window */ + switch(service) { + case twitter_service: + case wassr_service: + case identica_service: + case jisko_service: + attach_to_conv(conv, NULL); + break; + default: + twitter_debug("unknown service\n"); + break; + } + } +} + +static void +attach_to_conv(PurpleConversation *conv, gpointer null) +{ + PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); + GtkWidget *box, *sep, *counter, *menus; + GtkIMHtml *imhtml; + + box = gtkconv->toolbar; + imhtml = GTK_IMHTML(gtkconv->imhtml); + + /* Disable widgets that decorate or add link to composing text + * because Twitter cannot receive marked up string. For lean-view + * and wide-view, see pidgin/gtkimhtmltoolbar.c. + */ + menus = g_object_get_data(G_OBJECT(box), "lean-view"); + if(menus) { + gtk_widget_set_sensitive(GTK_WIDGET(menus), FALSE); + } + menus = g_object_get_data(G_OBJECT(box), "wide-view"); + if(menus) { + gtk_widget_set_sensitive(GTK_WIDGET(menus), FALSE); + } + + purple_conversation_set_features( + gtkconv->active_conv, + purple_conversation_get_features(gtkconv->active_conv) & + ~PURPLE_CONNECTION_HTML); + + /* check if the counter is enabled */ + if(!purple_prefs_get_bool(OPT_COUNTER)) + return; + + /* get counter object */ + counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter"); + g_return_if_fail(counter == NULL); + + /* make counter object */ + counter = gtk_label_new(NULL); + gtk_widget_set_name(counter, "counter_label"); + gtk_label_set_text(GTK_LABEL(counter), "0"); + gtk_box_pack_end(GTK_BOX(box), counter, FALSE, FALSE, 0); + gtk_widget_show_all(counter); + g_object_set_data(G_OBJECT(box), PLUGIN_ID "-counter", counter); + + /* make separator object */ + sep = gtk_vseparator_new(); + gtk_box_pack_end(GTK_BOX(box), sep, FALSE, FALSE, 0); + gtk_widget_show_all(sep); + g_object_set_data(G_OBJECT(box), PLUGIN_ID "-sep", sep); + + /* connect to signals */ + g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "insert_text", + G_CALLBACK(insert_text_cb), conv); + g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "delete_range", + G_CALLBACK(delete_text_cb), conv); + + /* redraw window */ + gtk_widget_queue_draw(pidgin_conv_get_window(gtkconv)->window); +} + +gboolean +is_twitter_account(PurpleAccount *account, const char *name) +{ + const gchar *proto = purple_account_get_protocol_id(account); + + if(g_strstr_len(name, 19, "twitter@twitter.com") && + g_strstr_len(proto, 11, "prpl-jabber")) { + return TRUE; + } + + return FALSE; +} + +static gboolean +is_twitter_conv(PurpleConversation *conv) +{ + g_return_val_if_fail(conv != NULL, FALSE); + + const char *name = purple_conversation_get_name(conv); + PurpleAccount *account = purple_conversation_get_account(conv); + + return is_twitter_account(account, name); +} + +static gboolean +is_wassr_account(PurpleAccount *account, const char *name) +{ + const gchar *proto = purple_account_get_protocol_id(account); + + if(g_strstr_len(name, 18, "wassr-bot@wassr.jp") && + g_strstr_len(proto, 11, "prpl-jabber")) { + return TRUE; + } + + return FALSE; +} + +static gboolean +is_wassr_conv(PurpleConversation *conv) +{ + g_return_val_if_fail(conv != NULL, FALSE); + + const char *name = purple_conversation_get_name(conv); + PurpleAccount *account = purple_conversation_get_account(conv); + + return is_wassr_account(account, name); +} + +static gboolean +is_identica_account(PurpleAccount *account, const char *name) +{ + const gchar *proto = purple_account_get_protocol_id(account); + + if(g_strstr_len(name, 16, "update@identi.ca") && + g_strstr_len(proto, 11, "prpl-jabber")) { + return TRUE; + } + + return FALSE; +} + +static gboolean +is_identica_conv(PurpleConversation *conv) +{ + g_return_val_if_fail(conv != NULL, FALSE); + + const char *name = purple_conversation_get_name(conv); + PurpleAccount *account = purple_conversation_get_account(conv); + + return is_identica_account(account, name); +} + +static gboolean +is_jisko_account(PurpleAccount *account, const char *name) +{ + const gchar *proto = purple_account_get_protocol_id(account); + + if(g_strstr_len(name, 16, "bot@jisko.net") && + g_strstr_len(proto, 11, "prpl-jabber")) { + return TRUE; + } + + return FALSE; +} + +static gboolean +is_jisko_conv(PurpleConversation *conv) +{ + g_return_val_if_fail(conv != NULL, FALSE); + + const char *name = purple_conversation_get_name(conv); + PurpleAccount *account = purple_conversation_get_account(conv); + + return is_jisko_account(account, name); +} + +gint +get_service_type_by_account(PurpleAccount *account, const char *sender) +{ + gint service = unknown_service; + + g_return_val_if_fail(account != NULL, unknown_service); + g_return_val_if_fail(sender != NULL, unknown_service); + + if(is_twitter_account(account, sender)) + service = twitter_service; + else if(is_wassr_account(account, sender)) + service = wassr_service; + else if(is_identica_account(account, sender)) + service = identica_service; + else if(is_jisko_account(account, sender)) + service = jisko_service; + + return service; +} + +gint +get_service_type(PurpleConversation *conv) +{ + gint service = unknown_service; + + g_return_val_if_fail(conv != NULL, unknown_service); + + if(is_twitter_conv(conv)) + service = twitter_service; + else if(is_wassr_conv(conv)) + service = wassr_service; + else if(is_identica_conv(conv)) + service = identica_service; + else if(is_jisko_conv(conv)) + service = jisko_service; + + return service; +} + +static void +conv_created_cb(PurpleConversation *conv, gpointer null) +{ + PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); + + twitter_debug("called\n"); + + g_return_if_fail(gtkconv != NULL); + + gint service = get_service_type(conv); + /* only attach to twitter conversation window */ + switch(service) { + case twitter_service: + get_status_with_api((gpointer)conv); + source.id = g_timeout_add_seconds( + purple_prefs_get_int(OPT_API_BASE_GET_INTERVAL), + get_status_with_api, (gpointer)conv); + source.conv = conv; + attach_to_conv(conv, NULL); + break; + case wassr_service: + case identica_service: + case jisko_service: + attach_to_conv(conv, NULL); + break; + default: + twitter_debug("unknown service\n"); + break; + } +} + +static void +deleting_conv_cb(PurpleConversation *conv) +{ + PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); + + twitter_debug("called\n"); + + g_return_if_fail(gtkconv != NULL); + + gint service = get_service_type(conv); + GHashTable *hash = NULL; + + /* only attach to twitter conversation window */ + switch(service) { + case twitter_service: + if(purple_prefs_get_bool(OPT_API_BASE_POST)) { + g_source_remove_by_user_data((gpointer)conv); + source.id = 0; + source.conv = NULL; + } + hash = icon_hash[twitter_service]; + break; + case wassr_service: + hash = icon_hash[wassr_service]; + break; + case identica_service: + hash = icon_hash[identica_service]; + break; + case jisko_service: + hash = icon_hash[jisko_service]; + break; + default: + twitter_debug("unknown service\n"); + break; + } + + if(hash) + delete_requested_icon_marks(gtkconv, hash); +} + +void +apply_filter(gchar **sender, gchar **buffer, PurpleMessageFlags *flags, int service) +{ + GMatchInfo *match_info; + const gchar *list = NULL; + gchar *screen_name = NULL; + gchar **candidates = NULL, **candidate = NULL; + + g_return_if_fail(*sender != NULL); + g_return_if_fail(*buffer != NULL); + + gchar *plain = strip_html_markup(*buffer); + + switch(service) { + case twitter_service: + default: + list = purple_prefs_get_string(OPT_FILTER_TWITTER); + screen_name = g_strdup_printf("@%s", purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER)); + break; + case wassr_service: + list = purple_prefs_get_string(OPT_FILTER_WASSR); + screen_name = g_strdup_printf("@%s", purple_prefs_get_string(OPT_SCREEN_NAME_WASSR)); + break; + case identica_service: + list = purple_prefs_get_string(OPT_FILTER_IDENTICA); + screen_name = g_strdup_printf("@%s", purple_prefs_get_string(OPT_SCREEN_NAME_IDENTICA)); + break; + case jisko_service: + list = purple_prefs_get_string(OPT_FILTER_JISKO); + screen_name = g_strdup_printf("@%s", purple_prefs_get_string(OPT_SCREEN_NAME_JISKO)); + break; + } + g_return_if_fail(list != NULL); + if(strstr(list, DEFAULT_LIST)) + return; + + /* find @myself */ + if(purple_prefs_get_bool(OPT_FILTER_EXCLUDE_REPLY) && + strstr(plain, screen_name)) { + g_free(plain); + g_free(screen_name); + return; + } + else + g_free(screen_name); + + candidates = g_strsplit_set(list, " ,:;", 0); + g_return_if_fail(candidates != NULL); + + g_regex_match(regp[SENDER], plain, 0, &match_info); + while(g_match_info_matches(match_info)) { + gchar *user = NULL; + user = g_match_info_fetch(match_info, 2); + twitter_debug("user = %s\n", user); + + for(candidate = candidates; *candidate; candidate++) { + if(!strcmp(*candidate, "")) + continue; + twitter_debug("candidate = %s\n", *candidate); + if(!strcmp(user, *candidate)) { + twitter_debug("match. filter %s\n", user); + /* pidgin should handle this flag properly --yaz */ +// *flags |= PURPLE_MESSAGE_INVISIBLE; + + /* temporal workaround */ + g_free(*sender); *sender = NULL; + g_free(*buffer); *buffer = NULL; + break; + } + } + + g_free(user); + g_match_info_next(match_info, NULL); + } + + g_free(plain); + g_strfreev(candidates); + g_match_info_free(match_info); +} + + +static gboolean +receiving_im_cb(PurpleAccount *account, char **sender, char **buffer, + PurpleConversation *conv, PurpleMessageFlags *flags, void *data) +{ + twitter_debug("called\n"); + twitter_debug("buffer = %s suppress_oops = %d\n", *buffer, suppress_oops); + + gint service; + + service = get_service_type_by_account(account, *sender); + twitter_debug("service = %d\n", service); + +#ifdef _WIN32 + /* suppress notification of incoming messages. */ + if(service != unknown_service && + purple_prefs_get_bool(OPT_PREVENT_NOTIFICATION)) { + if(!blink_modified) { + blink_modified = TRUE; + blink_state = purple_prefs_get_bool(OPT_PIDGIN_BLINK_IM); + purple_prefs_set_bool(OPT_PIDGIN_BLINK_IM, FALSE); + } + } + else { + if(blink_modified) { + purple_prefs_set_bool(OPT_PIDGIN_BLINK_IM, blink_state); + blink_modified = FALSE; + } + } +#endif + + if(service == wassr_service) { + gchar *stripped = strip_html_markup(*buffer); + /* suppress annoying completion message from wassr */ + if(strstr(*buffer, "チャンネル投稿完了:")) { + twitter_debug("clearing channel parrot message\n"); + g_free(*sender); *sender = NULL; + g_free(*buffer); *buffer = NULL; + } + /* discard parrot message */ + else { + GList *current = g_list_first(wassr_parrot_list); + while(current) { + GList *next = g_list_next(current); + + if(strstr(stripped, current->data)) { + twitter_debug("parrot clearing: buf = %s post = %s\n", + *buffer, (char *)current->data); + g_free(*sender); *sender = NULL; + g_free(*buffer); *buffer = NULL; + g_free(current->data); + current->data = NULL; + wassr_parrot_list = + g_list_delete_link(wassr_parrot_list, current); + break; + } + + current = next; + } + } + g_free(stripped); + } + + if(service == identica_service) { + /* discard parrot message */ + gchar *stripped = strip_html_markup(*buffer); + GList *current = g_list_first(identica_parrot_list); + while(current) { + GList *next = g_list_next(current); + + if(strstr(stripped, current->data)) { + twitter_debug("identica parrot clearing: buf = %s post = %s\n", + *buffer, (char *)current->data); + g_free(*sender); *sender = NULL; + g_free(*buffer); *buffer = NULL; + g_free(current->data); + current->data = NULL; + identica_parrot_list = + g_list_delete_link(identica_parrot_list, current); + break; + } + + current = next; + } + g_free(stripped); + } + + /* filtering */ + if(purple_prefs_get_bool(OPT_FILTER)) { + apply_filter(sender, buffer, flags, service); + } + + /* return here if it is not twitter */ + if(service != twitter_service) { + return FALSE; + } + + /* if we use api, discard all incoming IM messages. */ + if(purple_prefs_get_bool(OPT_API_BASE_POST)) { + g_free(*sender); *sender = NULL; + g_free(*buffer); *buffer = NULL; + } + + if(!suppress_oops || !purple_prefs_get_bool(OPT_SUPPRESS_OOPS)) + return FALSE; + + if(strstr(*buffer, OOPS_MESSAGE)) { + twitter_debug("clearing sender and buffer\n"); + g_free(*sender); *sender = NULL; + g_free(*buffer); *buffer = NULL; + suppress_oops = FALSE; + } + return FALSE; +} + + +static gboolean +displaying_im_cb(PurpleAccount *account, const char *who, char **message, + PurpleConversation *conv, PurpleMessageFlags flags, + void *unused) +{ + GtkIMHtml *imhtml; + GtkTextBuffer *text_buffer; + gint service = get_service_type(conv); + gint linenumber = 0; + + twitter_debug("called\n"); + + if(service == unknown_service) { + twitter_debug("neither twitter nor wassr conv\n"); + return FALSE; + } + + /* get text buffer */ + imhtml = GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml); + text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(imhtml)); + + /* store number of lines */ + linenumber = gtk_text_buffer_get_line_count(text_buffer); + g_hash_table_insert(conv_hash, conv, GINT_TO_POINTER(linenumber)); + twitter_debug("conv = %p linenumber = %d\n", conv, linenumber); + + return FALSE; +} + +static void +displayed_im_cb(PurpleAccount *account, const char *who, char *message, + PurpleConversation *conv, PurpleMessageFlags flags) +{ + GMatchInfo *match_info = NULL; + gchar *user_name = NULL; + GtkIMHtml *imhtml; + GtkTextBuffer *text_buffer; + GtkTextIter insertion_point; + gint service = get_service_type(conv); + icon_data *data = NULL; + gint linenumber; + GHashTable *hash = NULL; + gboolean renew = FALSE; + + twitter_debug("called\n"); + + if(service == unknown_service) { + twitter_debug("unknown service\n"); + return; + } + + /* get user's name */ + g_regex_match(regp[USER], message, 0, &match_info); + if(!g_match_info_matches(match_info)) { + twitter_debug("message was not matched : %s\n", message); + g_match_info_free(match_info); + return; + } + + user_name = g_match_info_fetch(match_info, 1); + g_match_info_free(match_info); + + /* insert icon */ + imhtml = GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml); + text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(imhtml)); + + /* get GtkTextIter in the target line */ + linenumber = GPOINTER_TO_INT(g_hash_table_lookup(conv_hash, conv)); + gtk_text_buffer_get_iter_at_line(text_buffer, + &insertion_point, + linenumber); + + switch(service) { + case twitter_service: + hash = icon_hash[twitter_service]; + break; + case wassr_service: + hash = icon_hash[wassr_service]; + break; + case identica_service: + hash = icon_hash[identica_service]; + break; + case jisko_service: + hash = icon_hash[jisko_service]; + break; + default: + twitter_debug("unknown service\n"); + break; + } + + if(hash) + data = g_hash_table_lookup(hash, user_name); + + if(data) { + /* check validity of icon */ + int count_thres = purple_prefs_get_int(OPT_ICON_MAX_COUNT); + int days_thres = DAYS_TO_SECONDS( + purple_prefs_get_int(OPT_ICON_MAX_DAYS)); + + if(data->use_count > count_thres || + (data->mtime && ((time(NULL) - data->mtime)) > days_thres)) { + twitter_debug("count=%d mtime=%d\n", + data->use_count, (int)(data->mtime)); + renew = TRUE; + request_icon(user_name, service, renew); + } + } + + /* if we don't have the icon for this user, put a mark instead and + * request the icon */ + if(!data || !data->pixbuf) { + twitter_debug("%s's icon is not in memory.\n", user_name); + mark_icon_for_user(gtk_text_buffer_create_mark( + text_buffer, NULL, &insertion_point, FALSE), + user_name, service); + /* request to attach icon to the buffer */ + request_icon(user_name, service, renew); + g_free(user_name); user_name = NULL; + return; + } + + /* if we have icon for this user, insert icon immediately */ + if(purple_prefs_get_bool(OPT_SHOW_ICON)) { + gtk_text_buffer_insert_pixbuf(text_buffer, + &insertion_point, + data->pixbuf); + data->use_count++; + } + g_free(user_name); user_name = NULL; + + twitter_debug("reach end of function\n"); +} + +static gboolean +load_plugin(PurplePlugin *plugin) +{ + int i; + + /* connect to signal */ + purple_signal_connect(purple_conversations_get_handle(), "writing-im-msg", + plugin, PURPLE_CALLBACK(writing_im_cb), NULL); + purple_signal_connect(purple_conversations_get_handle(), "sending-im-msg", + plugin, PURPLE_CALLBACK(sending_im_cb), NULL); + purple_signal_connect(purple_conversations_get_handle(), + "conversation-created", + plugin, PURPLE_CALLBACK(conv_created_cb), NULL); + purple_signal_connect(purple_conversations_get_handle(), "receiving-im-msg", + plugin, PURPLE_CALLBACK(receiving_im_cb), NULL); + purple_signal_connect(pidgin_conversations_get_handle(), "displaying-im-msg", + plugin, PURPLE_CALLBACK(displaying_im_cb), NULL); + + purple_signal_connect(pidgin_conversations_get_handle(), "displayed-im-msg", + plugin, PURPLE_CALLBACK(displayed_im_cb), NULL); + purple_signal_connect(purple_conversations_get_handle(), + "deleting-conversation", + plugin, PURPLE_CALLBACK(deleting_conv_cb), NULL); + purple_signal_connect(purple_connections_get_handle(), "signed-on", + plugin, PURPLE_CALLBACK(signed_on_cb), NULL); + + + /* compile regex */ + regp[RECIPIENT] = g_regex_new(P_RECIPIENT, 0, 0, NULL); + regp[SENDER] = g_regex_new(P_SENDER, 0, 0, NULL); + regp[COMMAND] = g_regex_new(P_COMMAND, G_REGEX_RAW, 0, NULL); + regp[PSEUDO] = g_regex_new(P_PSEUDO, G_REGEX_RAW, 0, NULL); + regp[USER] = g_regex_new(P_USER, 0, 0, NULL); + regp[CHANNEL_WASSR] = g_regex_new(P_CHANNEL, 0, 0, NULL); + regp[TAG_IDENTICA] = g_regex_new(P_TAG_IDENTICA, 0, 0, NULL); + regp[IMAGE_TWITTER] = g_regex_new(P_IMAGE_TWITTER, 0, 0, NULL); + regp[IMAGE_WASSR] = g_regex_new(P_IMAGE_WASSR, 0, 0, NULL); + regp[IMAGE_IDENTICA] = g_regex_new(P_IMAGE_IDENTICA, 0, 0, NULL); + regp[IMAGE_JISKO] = g_regex_new(P_IMAGE_JISKO, 0, 0, NULL); + regp[SIZE_128_WASSR] = g_regex_new(P_SIZE_128_WASSR, 0, 0, NULL); + regp[EXCESS_LF] = g_regex_new(P_EXCESS_LF, 0, 0, NULL); + + for(i = twitter_service; i < NUM_SERVICES; i++) { + icon_hash[i] = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + } + + conv_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, NULL); + + + /* attach counter to the existing twitter window */ + if(purple_prefs_get_bool(OPT_COUNTER)) { + attach_to_window(); + } + + return TRUE; +} + +static void +cancel_fetch_func(gpointer key, gpointer value, gpointer user_data) +{ + icon_data *data = (icon_data *)value; + + if(!data) + return; + + if(data->requested) { + purple_util_fetch_url_cancel(data->fetch_data); + data->fetch_data = NULL; + data->requested = FALSE; + } + + if(data->request_list) { + twitter_debug("somehow, request_list != NULL\n"); + } +} + +static void +cleanup_hash_entry_func(gpointer key, gpointer value, gpointer user_data) +{ + remove_marks_func(key, value, user_data); + cancel_fetch_func(key, value, user_data); +} + +static gboolean +unload_plugin(PurplePlugin *plugin) +{ + int i; + GList *current; + + twitter_debug("called\n"); + + /* disconnect from signal */ + purple_signal_disconnect(purple_conversations_get_handle(), + "writing-im-msg", + plugin, PURPLE_CALLBACK(writing_im_cb)); + purple_signal_disconnect(purple_conversations_get_handle(), + "sending-im-msg", + plugin, PURPLE_CALLBACK(sending_im_cb)); + purple_signal_disconnect(purple_conversations_get_handle(), + "conversation-created", + plugin, PURPLE_CALLBACK(conv_created_cb)); + purple_signal_disconnect(pidgin_conversations_get_handle(), + "displaying-im-msg", + plugin, PURPLE_CALLBACK(displaying_im_cb)); + purple_signal_disconnect(pidgin_conversations_get_handle(), + "displayed-im-msg", + plugin, PURPLE_CALLBACK(displayed_im_cb)); + purple_signal_disconnect(purple_conversations_get_handle(), + "receiving-im-msg", + plugin, PURPLE_CALLBACK(receiving_im_cb)); + purple_signal_disconnect(purple_conversations_get_handle(), + "deleting-conversation", + plugin, PURPLE_CALLBACK(deleting_conv_cb)); + purple_signal_disconnect(purple_connections_get_handle(), + "signed-on", + plugin, PURPLE_CALLBACK(signed_on_cb)); + + /* unreference regp */ + for(i = 0; i < NUM_REGPS; i++) { + g_regex_unref(regp[i]); + } + + /* remove mark list in each hash entry */ + /* cancel request that has not been finished yet */ + for(i = twitter_service; i < NUM_SERVICES; i++) { + /* delete mark list and stop requeset for each hash table */ + g_hash_table_foreach(icon_hash[i], + (GHFunc)cleanup_hash_entry_func, NULL); + /* destroy hash table for icon_data */ + g_hash_table_destroy(icon_hash[i]); + } + + g_hash_table_destroy(conv_hash); + + /* detach from twitter window */ + detach_from_window(); + + /* free wassr_parrot_list */ + current = g_list_first(wassr_parrot_list); + while(current) { + GList *next; + + next = g_list_next(current); + g_free(current->data); + wassr_parrot_list = + g_list_delete_link(wassr_parrot_list, current); + + current = next; + } + g_list_free(wassr_parrot_list); + wassr_parrot_list = NULL; + + /* free identica_parot_list */ + current = g_list_first(identica_parrot_list); + while(current) { + GList *next; + + next = g_list_next(current); + g_free(current->data); + identica_parrot_list = + g_list_delete_link(identica_parrot_list, current); + + current = next; + } + g_list_free(identica_parrot_list); + identica_parrot_list = NULL; + + return TRUE; +} + +static PidginPluginUiInfo ui_info = { + prefs_get_frame, + 0, /* page number - reserved */ + NULL, /* reserved 1 */ + NULL, /* reserved 2 */ + NULL, /* reserved 3 */ + NULL /* reserved 4 */ +}; + +static PurplePluginInfo info = { + PURPLE_PLUGIN_MAGIC, + PURPLE_MAJOR_VERSION, + PURPLE_MINOR_VERSION, + PURPLE_PLUGIN_STANDARD, /**< type */ + PIDGIN_PLUGIN_TYPE, /**< ui_req */ + 0, /**< flags */ + NULL, /**< deps */ + PURPLE_PRIORITY_DEFAULT, /**< priority */ + PLUGIN_ID, /**< id */ + "Pidgin-Twitter", /**< name */ + "0.9.0d1", /**< version */ + "provides useful features for twitter", /** summary */ + "provides useful features for twitter", /** desc */ + "Yoshiki Yazawa, mikanbako, \nKonosuke Watanabe, IWATA Ray, \nmojin, umq, \nthe pidging-twitter team", /**< author */ + "http://www.honeyplanet.jp/pidgin-twitter/", /**< homepage */ + load_plugin, /**< load */ + unload_plugin, /**< unload */ + NULL, /**< destroy */ + &ui_info, /**< ui_info */ + NULL, /**< extra_info */ + NULL, /**< pref info */ + NULL +}; + +static void +init_plugin(PurplePlugin *plugin) +{ + char *dirname = NULL; + + g_type_init(); + dirname = g_build_filename(purple_user_dir(), "pidgin-twitter", "icons", NULL); + if(dirname) + purple_prefs_add_string(OPT_ICON_DIR, dirname); + g_free(dirname); + + /* add plugin preferences */ + purple_prefs_add_none(OPT_PIDGINTWITTER); + purple_prefs_add_bool(OPT_TRANSLATE_RECIPIENT, TRUE); + purple_prefs_add_bool(OPT_TRANSLATE_SENDER, TRUE); + purple_prefs_add_bool(OPT_TRANSLATE_CHANNEL, TRUE); + purple_prefs_add_bool(OPT_ESCAPE_PSEUDO, TRUE); + purple_prefs_add_bool(OPT_STRIP_EXCESS_LF, TRUE); + + purple_prefs_add_bool(OPT_PLAYSOUND_RECIPIENT, TRUE); + purple_prefs_add_bool(OPT_PLAYSOUND_SENDER, TRUE); + purple_prefs_add_int(OPT_SOUNDID_RECIPIENT, PURPLE_SOUND_POUNCE_DEFAULT); + purple_prefs_add_string(OPT_USERLIST_RECIPIENT, DEFAULT_LIST); + purple_prefs_add_int(OPT_SOUNDID_SENDER, PURPLE_SOUND_POUNCE_DEFAULT); + purple_prefs_add_string(OPT_USERLIST_SENDER, DEFAULT_LIST); + + purple_prefs_add_bool(OPT_COUNTER, TRUE); + purple_prefs_add_bool(OPT_SUPPRESS_OOPS, TRUE); + purple_prefs_add_bool(OPT_PREVENT_NOTIFICATION, FALSE); + + purple_prefs_add_bool(OPT_API_BASE_POST, FALSE); + purple_prefs_add_int(OPT_API_BASE_GET_INTERVAL, TWITTER_DEFAULT_INTERVAL); + purple_prefs_add_string(OPT_SCREEN_NAME_TWITTER, EMPTY); + purple_prefs_add_string(OPT_PASSWORD_TWITTER, EMPTY); + purple_prefs_add_string(OPT_SCREEN_NAME_WASSR, EMPTY); + purple_prefs_add_string(OPT_SCREEN_NAME_IDENTICA, EMPTY); + purple_prefs_add_string(OPT_SCREEN_NAME_JISKO, EMPTY); + + purple_prefs_add_bool(OPT_SHOW_ICON, TRUE); + purple_prefs_add_int(OPT_ICON_SIZE, DEFAULT_ICON_SIZE); + purple_prefs_add_bool(OPT_UPDATE_ICON, TRUE); + purple_prefs_add_int(OPT_ICON_MAX_COUNT, DEFAULT_ICON_MAX_COUNT); + purple_prefs_add_int(OPT_ICON_MAX_DAYS, DEFAULT_ICON_MAX_DAYS); + purple_prefs_add_bool(OPT_LOG_OUTPUT, FALSE); + + purple_prefs_add_bool(OPT_FILTER, TRUE); + purple_prefs_add_bool(OPT_FILTER_EXCLUDE_REPLY, TRUE); + purple_prefs_add_string(OPT_FILTER_TWITTER, DEFAULT_LIST); + purple_prefs_add_string(OPT_FILTER_WASSR, DEFAULT_LIST); + purple_prefs_add_string(OPT_FILTER_IDENTICA, DEFAULT_LIST); + purple_prefs_add_string(OPT_FILTER_JISKO, DEFAULT_LIST); +} + +PURPLE_INIT_PLUGIN(pidgin_twitter, init_plugin, info) diff -r a37ae6c8fa66 -r c2620a99622b pidgin-twitter.c --- a/pidgin-twitter.c Fri Nov 21 22:22:33 2008 +0900 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3413 +0,0 @@ -/* - * Pidgin-Twitter plugin. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. - */ -#define PURPLE_PLUGINS 1 - -#include "pidgin-twitter.h" - - -/***********/ -/* globals */ -/***********/ -#define NUM_REGPS 13 -#define NUM_SERVICES 4 /* twitter, wassr, identica, jisko. */ -static GRegex *regp[NUM_REGPS]; -static gboolean suppress_oops = FALSE; -static GHashTable *icon_hash[NUM_SERVICES]; -static GHashTable *conv_hash = NULL; -static GList *statuseslist = NULL; -static GList *postedlist = NULL; -static GList *wassr_parrot_list = NULL; -static GList *identica_parrot_list = NULL; -#ifdef _WIN32 -static gboolean blink_state = FALSE; -static gboolean blink_modified = FALSE; -#endif - -static struct _source { - guint id; - PurpleConversation *conv; -} source; - -#ifndef _WIN32 -extern gchar *sanitize_utf(const gchar *msg, gsize len, gsize *newlen) __attribute__ ((weak)); -#endif - -/*************/ -/* functions */ -/*************/ - -/* this function has been taken from autoaccept plugin */ -static gboolean -ensure_path_exists(const char *dir) -{ - if (!g_file_test(dir, G_FILE_TEST_IS_DIR)) { - if (purple_build_dir(dir, S_IRUSR | S_IWUSR | S_IXUSR)) - return FALSE; - } - - return TRUE; -} - - -/**********************/ -/* our implementation */ -/**********************/ - -static gchar * -twitter_memrchr(const gchar *s, int c, size_t n) -{ - int nn = n; - - g_return_val_if_fail(s != NULL, NULL); - - while(nn+1) { - if((int)*(s+nn) == c) - return (gchar *)(s+nn); - nn--; - } - return NULL; -} - -static gchar *html_tags[] = { - "", - "", - "", - "

", - "

", - "
", - "", - "", - "", - "", - "", - "", - "
", - "
", - "", - "", - NULL -}; - -static gchar * -strip_html_markup(const gchar *src) -{ - gchar *head, *tail; /* head and tail of html */ - gchar *begin, *end; /* begin:< end:> */ - gchar *html, *str; /* copied src and string to be returned */ -/* gchar *vis1, *vis2; */ /* begin and end of address part */ - gchar *startp; /* starting point marker */ - gchar **tagp; /* tag iterator */ - gchar *tmp, *tmp2; /* scratches */ - - g_return_val_if_fail(src != NULL, NULL); - - const gchar *ptr, *ent; - gchar *ptr2; - gint entlen; - - /* unescape &x; */ - html = g_malloc0(strlen(src) + 1); - ptr2 = html; - for(ptr = src; *ptr; ) { - if(*ptr == '&') { - ent = purple_markup_unescape_entity(ptr, &entlen); - if(ent != NULL) { - while(*ent) { - *ptr2++ = *ent++; - } - ptr += entlen; - } - else { - *ptr2++ = *ptr++; - } - } - else { - *ptr2++ = *ptr++; - } - } /* for */ - - str = g_strdup("\0"); - - head = html; - tail = head + strlen(html); - startp = head; - -loop: - begin = NULL; - end = NULL; - - if(startp >= tail) { - g_free(html); - return str; - } - - end = strchr(startp, '>'); - if(end) { - begin = twitter_memrchr(startp, '<', end - startp); - if(begin < startp) - begin = NULL; - - if(!begin) { /* '>' found but no corresponding '<' */ - tmp = g_strndup(startp, end - startp + 1); /* concat until '>' */ - tmp2 = g_strconcat(str, tmp, NULL); - g_free(str); - g_free(tmp); - str = tmp2; - startp = end + 1; - goto loop; - } - } - else { /* neither '>' nor '<' were found */ - tmp = g_strconcat(str, startp, NULL); /* concat the rest */ - g_free(str); - str = tmp; - g_free(html); - return str; - } - - /* here, both < and > are found */ - /* concatenate leading part to dest */ - tmp = g_strndup(startp, begin - startp); - tmp2 = g_strconcat(str, tmp, NULL); - g_free(tmp); - g_free(str); - str = tmp2; - - /* find tag */ - for(tagp = html_tags; *tagp; tagp++) { - if(!g_ascii_strncasecmp(begin, *tagp, strlen(*tagp))) { - /* we found a valid tag */ - /* if tag is
. */ - startp = end + 1; - goto loop; - } -#else - /* anything else: discard whole <>. */ - startp = end + 1; - goto loop; -#endif - } /* valid tag */ - } - - /* no valid tag was found: copy */ - tmp = g_strndup(begin, end - begin + 1); - tmp2 = g_strconcat(str, tmp, NULL); - g_free(tmp); - g_free(str); - str = tmp2; - startp = end + 1; - goto loop; -} - -/* string utilities */ -static void -escape(gchar **str) -{ - GMatchInfo *match_info = NULL; - gchar *newstr = NULL, *match = NULL; - gboolean flag = FALSE; - - /* search genuine command */ - g_regex_match(regp[COMMAND], *str, 0, &match_info); - while(g_match_info_matches(match_info)) { - match = g_match_info_fetch(match_info, 1); - twitter_debug("command = %s\n", match); - g_free(match); - g_match_info_next(match_info, NULL); - flag = TRUE; - } - g_match_info_free(match_info); - match_info = NULL; - - if(flag) - return; - - /* if not found, check pseudo command */ - g_regex_match(regp[PSEUDO], *str, 0, &match_info); - while(g_match_info_matches(match_info)) { - match = g_match_info_fetch(match_info, 1); - twitter_debug("pseudo = %s\n", match); - g_free(match); - g_match_info_next(match_info, NULL); - flag = TRUE; - } - g_match_info_free(match_info); - match_info = NULL; - - /* if there is pseudo one, escape it */ - if(flag) { - /* put ". " to the beginning of buffer */ - newstr = g_strdup_printf(". %s", *str); - twitter_debug("*str = %s newstr = %s\n", *str, newstr); - g_free(*str); - *str = newstr; - } -} - -static void -strip_markup(gchar **str, gboolean escape) -{ - gchar *plain; - - plain = strip_html_markup(*str); - g_free(*str); - if(escape) { - *str = g_markup_escape_text(plain, -1); - g_free(plain); - } - else { - *str = plain; - } - twitter_debug("result=%s\n", *str); -} - - -/**************************/ -/* API base get functions */ -/**************************/ -/* xml parser */ -static void -parse_user(xmlNode *user, status_t *st) -{ - xmlNode *nptr; - - for(nptr = user->children; nptr != NULL; nptr = nptr->next) { - if(nptr->type == XML_ELEMENT_NODE) { - if(!xmlStrcmp(nptr->name, (xmlChar *)"screen_name")) { - gchar *str = (gchar *)xmlNodeGetContent(nptr); - st->screen_name = g_strdup(str); - xmlFree(str); - } - else if(!xmlStrcmp(nptr->name, (xmlChar *)"profile_image_url")) { - gchar *str = (gchar *)xmlNodeGetContent(nptr); - st->profile_image_url = g_strdup(str); - xmlFree(str); - } - } - } -} - -static gchar *day_of_week_name[] = { - "Sun", - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - NULL -}; - -static gchar *month_name[] = { - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - NULL -}; - -static void -read_timestamp(const char *str, struct tm *res) -{ - char day_of_week[4]; - char month[4]; - char time_offset[6]; - int day, hour, minute, second, year; - int i; - - if(str == NULL || res == NULL) - return; - - sscanf(str, "%s %s %d %d:%d:%d %s %d", - day_of_week, month, &day, - &hour, &minute, &second, - time_offset, &year); - - for(i=0; i<7; i++) { - if(!strcmp(day_of_week_name[i], day_of_week)) { - res->tm_wday = i; - } - } - for(i=0; i<12; i++) { - if(!strcmp(month_name[i], month)) { - res->tm_mon = i; - } - } - - res->tm_mday = day; - res->tm_hour = hour; - res->tm_min = minute; - res->tm_sec = second; - res->tm_year = year - 1900; -#ifndef _WIN32 - int offset = atoi(time_offset); - res->tm_gmtoff = -1 * (60 * 60 * offset / 100); -#endif - -} - -static void -parse_status(xmlNode *status, status_t *st) -{ - xmlNode *nptr; - - for(nptr = status->children; nptr != NULL; nptr = nptr->next) { - if(nptr->type == XML_ELEMENT_NODE) { - if(!xmlStrcmp(nptr->name, (xmlChar *)"created_at")) { - gchar *str = (gchar *)xmlNodeGetContent(nptr); - st->created_at = g_strdup(str); - - /* read time stamp */ - struct tm res; - memset(&res, 0x00, sizeof(struct tm)); - read_timestamp(str, &res); - tzset(); -#ifdef _WIN32 - st->time = mktime(&res) - timezone; -#else - st->time = mktime(&res) + res.tm_gmtoff; -#endif - - xmlFree(str); - } - else if(!xmlStrcmp(nptr->name, (xmlChar *)"id")) { - gchar *str = (gchar *)xmlNodeGetContent(nptr); - st->id = atoi(str); - xmlFree(str); - } - else if(!xmlStrcmp(nptr->name, (xmlChar *)"text")) { - gchar *str = (gchar *)xmlNodeGetContent(nptr); - st->text = g_strdup(str); - xmlFree(str); - } - else if(!xmlStrcmp(nptr->name, (xmlChar *)"user")) { - parse_user(nptr, st); - } - } - } -} - -static void -free_status(status_t *st) -{ - g_free(st->created_at); - g_free(st->text); - g_free(st->screen_name); - g_free(st->profile_image_url); -} - -static gboolean -is_posted_message(status_t *status, guint lastid) -{ - GList *pp = g_list_first(postedlist); - gboolean rv = FALSE; - - while(pp) { - GList *next; - status_t *posted = (status_t *)pp->data; - - next = g_list_next(pp); - - if(posted->id == status->id) { - rv = TRUE; - } - - if(posted->id <= lastid) { - free_status(posted); - g_free(pp->data); - postedlist = g_list_delete_link(postedlist, pp); - } - - pp = next; - } - - return rv; -} - -static void -get_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, - const gchar *url_text, size_t len, - const gchar *error_message) - -{ - xmlDocPtr doc; - xmlNode *nptr, *nptr2; - static guint lastid = 0; - PurpleConversation *conv; - GList *stp; - const gchar *start; - - g_return_if_fail(url_text != NULL); - - conv = (PurpleConversation *)user_data; - if(!conv) - return; - - /* skip to the beginning of xml */ - start = strstr(url_text, "children; nptr != NULL; nptr = nptr->next) { - if(nptr->type == XML_ELEMENT_NODE && - !xmlStrcmp(nptr->name, (xmlChar *)"statuses")) { - - for(nptr2 = nptr->children; nptr2 != NULL; nptr2 = nptr2->next) { - if(nptr2->type == XML_ELEMENT_NODE && - !xmlStrcmp(nptr2->name, (xmlChar *)"status")) { - status_t *st = g_new0(status_t, 1); - statuseslist = g_list_prepend(statuseslist, st); - parse_status(nptr2, st); - } - } - } - } - - xmlFreeDoc(doc); - xmlCleanupParser(); - - /* process statuseslist */ - stp = g_list_first(statuseslist); - while(stp) { - GList *next; - status_t *st = (status_t *)stp->data; - - next = g_list_next(stp); - - if(st->id > lastid && !is_posted_message(st, lastid)) { - gchar *msg = NULL; - gchar *sender = NULL; - - sender = g_strdup("twitter@twitter.com"); - - PurpleMessageFlags flag = PURPLE_MESSAGE_RECV; - - msg = g_strdup_printf("%s: %s", st->screen_name, st->text); - - /* apply filter*/ - if(purple_prefs_get_bool(OPT_FILTER)) { - apply_filter(&sender, &msg, &flag, twitter_service); - } - if(sender && msg) { - purple_conv_im_write(conv->u.im, - sender, - msg, - flag, - st->time); - } - lastid = st->id; - - g_free(sender); - g_free(msg); - } - - free_status(st); - g_free(stp->data); - statuseslist = g_list_delete_link(statuseslist, stp); - - stp = next; - } -} - -/* status fetching function. it will be called periodically. */ -static gboolean -get_status_with_api(gpointer data) -{ - /* fetch friends time line */ - char *request, *header; - char *basic_auth, *basic_auth_encoded; - - twitter_debug("called\n"); - - /* if disabled, just return */ - if(!purple_prefs_get_bool(OPT_API_BASE_POST)) - return TRUE; - - const char *screen_name = - purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER); - const char *password = - purple_prefs_get_string(OPT_PASSWORD_TWITTER); - - if (!screen_name || !password || !screen_name[0] || !password[0]) { - twitter_debug("screen_name or password is empty\n"); - return TRUE; - } - - /* auth */ - basic_auth = g_strdup_printf("%s:%s", screen_name, password); - basic_auth_encoded = purple_base64_encode((unsigned char *)basic_auth, - strlen(basic_auth)); - g_free(basic_auth); - - /* header */ - header = g_strdup_printf(TWITTER_STATUS_GET, basic_auth_encoded); - request = g_strconcat(header, "\r\n", NULL); - - /* invoke fetch */ - purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE, - NULL, TRUE, request, TRUE, - get_status_with_api_cb, data); - - g_free(header); - g_free(basic_auth_encoded); - g_free(request); - - return TRUE; -} - -/****************************/ -/* API based post functions */ -/****************************/ -static void -post_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, - const gchar *url_text, size_t len, - const gchar *error_message) -{ - twitter_message_t *tm = (twitter_message_t *)user_data; - gchar *msg = NULL; - char *p1 = NULL, *p2 = NULL; - int error = 1; - PurpleConversation *conv; - - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, - "twitter@twitter.com", - tm->account); - if (!conv) { - twitter_debug("failed to get conversation\n"); - goto fin; - } - - if (error_message) { - /* connection failed or something */ - msg = g_strdup_printf("Local error: %s", error_message); - } else { - int code = -1; - - if ((strncmp(url_text, "HTTP/1.0", strlen("HTTP/1.0")) == 0 - || strncmp(url_text, "HTTP/1.1", strlen("HTTP/1.1")) == 0)) { - - p1 = strchr(url_text, ' '); - - if (p1) { - p1++; - p2 = strchr(p1, ' '); - if (p2) - p2++; - else - p2 = NULL; - } - } - - code = atoi(p1); - - if (code == 200) { - error = 0; - } else { - switch (code) { - case 400: - msg = g_strdup("Invalid request. Too many updates?"); - break; - case 401: - msg = g_strdup("Authorization failed."); - break; - case 403: - msg = g_strdup("Your update has been refused by Twitter server " - "for some reason."); - break; - case 404: - msg = g_strdup("Requested URI is not found."); - break; - case 500: - msg = g_strdup("Server error."); - break; - case 502: - msg = g_strdup("Twitter is down or under maintenance."); - break; - case 503: - msg = g_strdup("Twitter is extremely crowded. " - "Try again later."); - break; - default: - msg = g_strdup_printf("Unknown error. (%d %s)", - code, p2 ? p2 : ""); - break; - } - } - } - - if (!error) { - purple_conv_im_write(conv->u.im, - purple_account_get_username(tm->account), - tm->status, PURPLE_MESSAGE_SEND, tm->time); - - /* cache message ID that posted via API */ - gchar *start = NULL; - xmlDocPtr doc; - xmlNode *nptr; - - start = strstr(url_text, "children; nptr != NULL; nptr = nptr->next) { - if(nptr->type == XML_ELEMENT_NODE && - !xmlStrcmp(nptr->name, (xmlChar *)"status")) { - status_t *st = g_new0(status_t, 1); - postedlist = g_list_prepend(postedlist, st); - parse_status(nptr, st); - } - } - - xmlFreeDoc(doc); - xmlCleanupParser(); - - } else { - gchar *m; - m = g_strdup_printf("%s
%s", - msg, tm->status); - /* FIXME: too strong. it should be more smart */ - purple_conv_im_write(conv->u.im, - purple_account_get_username(tm->account), - m, PURPLE_MESSAGE_ERROR, time(NULL)); - g_free(m); - } - - fin: - if (msg) - g_free(msg); - - if (tm) { - if (tm->status) - g_free(tm->status); - g_free(tm); - } - -} - -static void -post_status_with_api(PurpleAccount *account, char **buffer) -{ - char *request, *status, *header; - const char *url_encoded = purple_url_encode(*buffer); - char *basic_auth, *basic_auth_encoded; - - twitter_message_t *tm; - - const char *screen_name = - purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER); - const char *password = purple_prefs_get_string(OPT_PASSWORD_TWITTER); - - twitter_debug("tm.account: %s\n", - purple_account_get_username(account)); - - if (!screen_name || !password || !screen_name[0] || !password[0]) { - twitter_debug("screen_name or password is empty\n"); - return; - } - - tm = g_new(twitter_message_t, 1); - tm->account = account; - tm->status = g_strdup(*buffer); - tm->time = time(NULL); - - basic_auth = g_strdup_printf("%s:%s", screen_name, password); - basic_auth_encoded = purple_base64_encode((unsigned char *)basic_auth, - strlen(basic_auth)); - g_free(basic_auth); - - status = g_strdup_printf(TWITTER_STATUS_FORMAT, url_encoded); - header = g_strdup_printf(TWITTER_STATUS_POST, basic_auth_encoded, - (int)strlen(status)); - - request = g_strconcat(header, "\r\n", status, "\r\n", NULL); - - purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE, - NULL, TRUE, request, TRUE, - post_status_with_api_cb, tm); - - g_free(header); - g_free(basic_auth_encoded); - g_free(status); - g_free(request); - -} - -/***********************/ -/* intrinsic functions */ -/***********************/ -static gboolean -sending_im_cb(PurpleAccount *account, char *recipient, char **buffer, - void *data) -{ - int utflen, bytes; - gboolean twitter_ac = FALSE, wassr_ac = FALSE, identica_ac = FALSE; - twitter_debug("called\n"); - - twitter_ac = is_twitter_account(account, recipient); - wassr_ac = is_wassr_account(account, recipient); - identica_ac = is_identica_account(account, recipient); - - /* strip all markups */ - if(twitter_ac || wassr_ac || identica_ac) { - gchar *tmp, *plain; - gsize dummy; - - tmp = strip_html_markup(*buffer); - -#ifndef _WIN32 - if(sanitize_utf) { - plain = sanitize_utf(tmp, -1, &dummy); - g_free(tmp); - } - else -#endif - plain = tmp; - - if(wassr_ac) { - /* store sending message to address parrot problem */ - wassr_parrot_list = - g_list_prepend(wassr_parrot_list, g_strdup(plain)); - twitter_debug("wassr parrot pushed:%s\n", plain); - } - else if(identica_ac) { - /* store sending message to address parrot problem */ - identica_parrot_list = - g_list_prepend(identica_parrot_list, g_strdup(plain)); - twitter_debug("identica parrot pushed:%s\n", plain); - } - - g_free(*buffer); - *buffer = g_markup_escape_text(plain, -1); - g_free(plain); - } - - /* return here if the message is not to twitter */ - if(!twitter_ac) - return FALSE; - - /* escape pseudo command */ - if(twitter_ac && - purple_prefs_get_bool(OPT_ESCAPE_PSEUDO)) { - escape(buffer); - } - - /* update status with Twitter API instead of IM protocol */ - if (purple_prefs_get_bool(OPT_API_BASE_POST)) { - if (buffer && *buffer) { - post_status_with_api(account, buffer); - (*buffer)[0] = '\0'; - } - return FALSE; - } - - /* try to suppress oops message */ - utflen = g_utf8_strlen(*buffer, -1); - bytes = strlen(*buffer); - twitter_debug("utflen = %d bytes = %d\n", utflen, bytes); - if(bytes > 140 && utflen <= 140) - suppress_oops = TRUE; - - return FALSE; -} - -static gboolean -eval(const GMatchInfo *match_info, GString *result, gpointer user_data) -{ - eval_data *data = (eval_data *)user_data; - gint which = data->which; - gint service = data->service; - gchar sub[SUBST_BUF_SIZE]; - - twitter_debug("which = %d service = %d\n", which, service); - - if(which == RECIPIENT) { - gchar *match1 = g_match_info_fetch(match_info, 1); /* preceding \s */ - gchar *match2 = g_match_info_fetch(match_info, 2); /* recipient */ - const gchar *format = NULL; - - switch(service) { - case twitter_service: - format = RECIPIENT_FORMAT_TWITTER; - break; - case wassr_service: - format = RECIPIENT_FORMAT_WASSR; - break; - case identica_service: - format = RECIPIENT_FORMAT_IDENTICA; - break; - case jisko_service: - format = RECIPIENT_FORMAT_JISKO; - break; - default: - twitter_debug("unknown service\n"); - break; - } - g_snprintf(sub, SUBST_BUF_SIZE, format, match1 ? match1: "", match2, match2); - g_free(match1); - g_free(match2); - } - else if(which == SENDER) { - gchar *match1 = g_match_info_fetch(match_info, 1); /*preceding CR|LF*/ - gchar *match2 = g_match_info_fetch(match_info, 2); /* sender */ - const gchar *format = NULL; - - switch(service) { - case twitter_service: - format = SENDER_FORMAT_TWITTER; - break; - case wassr_service: - format = SENDER_FORMAT_WASSR; - break; - case identica_service: - format = SENDER_FORMAT_IDENTICA; - break; - case jisko_service: - format = SENDER_FORMAT_JISKO; - break; - default: - twitter_debug("unknown service\n"); - break; - } - - g_snprintf(sub, SUBST_BUF_SIZE, format, match1 ? match1: "", match2, match2); - - g_free(match1); - g_free(match2); - } - else if(which == CHANNEL_WASSR && service == wassr_service) { - gchar *match1 = g_match_info_fetch(match_info, 1); /*before channel*/ - gchar *match2 = g_match_info_fetch(match_info, 2); /* channel */ - const gchar *format = CHANNEL_FORMAT_WASSR; - - g_snprintf(sub, SUBST_BUF_SIZE, format, match1 ? match1: "", match2, match2); - - g_free(match1); - g_free(match2); - } - else if(which == TAG_IDENTICA && service == identica_service) { - gchar *match = g_match_info_fetch(match_info, 1); - gchar *link = g_ascii_strdown(match, -1); - purple_str_strip_char(link, '-'); - purple_str_strip_char(link, '_'); - const gchar *format = TAG_FORMAT_IDENTICA; - g_snprintf(sub, SUBST_BUF_SIZE, format, link, match); - g_free(match); - g_free(link); - } - else if(which == EXCESS_LF) { - g_snprintf(sub, SUBST_BUF_SIZE, "%s", "\n"); - } - - g_string_append(result, sub); - twitter_debug("sub = %s\n", sub); - - return FALSE; -} - -static void -translate(gchar **str, gint regp_id, gint service) -{ - gchar *newstr; - eval_data *data = g_new0(eval_data, 1); - - data->which = regp_id; - data->service = service; - - newstr = g_regex_replace_eval(regp[regp_id], /* compiled regex */ - *str, /* subject string */ - -1, /* length of the subject string */ - 0, /* start position */ - 0, /* match options */ - eval, /* function to be called for each match */ - data, /* user data */ - NULL); /* error handler */ - - g_free(data); data = NULL; - - twitter_debug("which = %d *str = %s newstr = %s\n", regp_id, *str, newstr); - - g_free(*str); - *str = newstr; -} - -static void -playsound(gchar **str, gint which) -{ - GMatchInfo *match_info; - const gchar *list = NULL; - gchar **candidates = NULL, **candidate = NULL; - - list = purple_prefs_get_string(which ? OPT_USERLIST_SENDER : - OPT_USERLIST_RECIPIENT); - g_return_if_fail(list != NULL); - if(strstr(list, DEFAULT_LIST)) - return; - - candidates = g_strsplit_set(list, " ,:;", 0); - g_return_if_fail(candidates != NULL); - - g_regex_match(regp[which], *str, 0, &match_info); - while(g_match_info_matches(match_info)) { - gchar *user = NULL; - if(which == RECIPIENT) - user = g_match_info_fetch(match_info, 2); - else if(which == SENDER) - user = g_match_info_fetch(match_info, 2); - twitter_debug("user = %s\n", user); - - for(candidate = candidates; *candidate; candidate++) { - if(!strcmp(*candidate, "")) - continue; - twitter_debug("candidate = %s\n", *candidate); - if(!strcmp(user, *candidate)) { - twitter_debug("match. play sound\n"); - purple_sound_play_event(purple_prefs_get_int - (which ? OPT_SOUNDID_SENDER : - OPT_SOUNDID_RECIPIENT), NULL); - break; - } - } - g_free(user); - g_match_info_next(match_info, NULL); - } - g_strfreev(candidates); - g_match_info_free(match_info); -} - -static gboolean -writing_im_cb(PurpleAccount *account, char *sender, char **buffer, - PurpleConversation *conv, int flags, void *data) -{ - twitter_debug("called\n"); - - gint service = get_service_type(conv); - - /* check if the conversation is between twitter */ - if(service == unknown_service) - return FALSE; - - /* add screen_name if the current message is posted by myself */ - if (flags & PURPLE_MESSAGE_SEND) { - gchar *m = NULL; - const char *screen_name = NULL; - - switch(service) { - case twitter_service: - screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER); - break; - case wassr_service: - screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_WASSR); - break; - case identica_service: - screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_IDENTICA); - break; - case jisko_service: - screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_JISKO); - break; - } - - if (screen_name) { - m = g_strdup_printf("%s: %s", screen_name, *buffer); - g_free(*buffer); - *buffer = m; - } - } - - /* strip all markups */ - strip_markup(buffer, TRUE); - - /* playsound */ - if(purple_prefs_get_bool(OPT_PLAYSOUND_SENDER)) { - playsound(buffer, SENDER); - } - if(purple_prefs_get_bool(OPT_PLAYSOUND_RECIPIENT)) { - playsound(buffer, RECIPIENT); - } - - /* translate */ - if(purple_prefs_get_bool(OPT_TRANSLATE_SENDER)) { - translate(buffer, SENDER, service); - } - if(purple_prefs_get_bool(OPT_TRANSLATE_RECIPIENT)) { - translate(buffer, RECIPIENT, service); - } - if(service == wassr_service && - purple_prefs_get_bool(OPT_TRANSLATE_CHANNEL)) { - translate(buffer, CHANNEL_WASSR, service); - } - if(service == identica_service && - purple_prefs_get_bool(OPT_TRANSLATE_CHANNEL)) { - translate(buffer, TAG_IDENTICA, service); - } - - /* escape pseudo command (to show the same result as sending message) */ - if(service == twitter_service && - purple_prefs_get_bool(OPT_ESCAPE_PSEUDO)) { - escape(buffer); - } - - if(purple_prefs_get_bool(OPT_STRIP_EXCESS_LF)) { - translate(buffer, EXCESS_LF, service); - } - - return FALSE; -} - -static void -insert_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *position, - gchar *new_text, gint new_text_length, gpointer user_data) -{ - PurpleConversation *conv = (PurpleConversation *)user_data; - PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); - - GtkWidget *box, *counter = NULL; - gchar *markup = NULL; - gint service = get_service_type(conv); - guint count; - - g_return_if_fail(gtkconv != NULL); - - switch(service) { - case twitter_service: - case identica_service: - case jisko_service: - count = gtk_text_buffer_get_char_count(textbuffer) + - (unsigned int)g_utf8_strlen(new_text, -1); - markup = g_markup_printf_escaped("%u", - count <= 140 ? "black" : "red", count); - break; - case wassr_service: - count = gtk_text_buffer_get_char_count(textbuffer) + - (unsigned int)g_utf8_strlen(new_text, -1); - markup = g_markup_printf_escaped("%u", - count <= 255 ? "black" : "red", count); - break; - default: - twitter_debug("unknown service\n"); - break; - } - - box = gtkconv->toolbar; - counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter"); - if(counter) - gtk_label_set_markup(GTK_LABEL(counter), markup); - - g_free(markup); -} - -static void -delete_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *start_pos, - GtkTextIter *end_pos, gpointer user_data) -{ - PurpleConversation *conv = (PurpleConversation *)user_data; - PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); - GtkWidget *box, *counter = NULL; - gchar *markup = NULL; - gint service = get_service_type(conv); - guint count = 0; - - g_return_if_fail(gtkconv != NULL); - - switch(service) { - case twitter_service: - case identica_service: - case jisko_service: - count= gtk_text_buffer_get_char_count(textbuffer) - - (gtk_text_iter_get_offset(end_pos) - - gtk_text_iter_get_offset(start_pos)); - markup = g_markup_printf_escaped("%u", - count <= 140 ? "black" : "red", count); - break; - case wassr_service: - count= gtk_text_buffer_get_char_count(textbuffer) - - (gtk_text_iter_get_offset(end_pos) - - gtk_text_iter_get_offset(start_pos)); - markup = g_markup_printf_escaped("%u", - count <= 255 ? "black" : "red", count); - break; - default: - twitter_debug("unknown service\n"); - break; - } - - box = gtkconv->toolbar; - counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter"); - if(counter) - gtk_label_set_markup(GTK_LABEL(counter), markup); - - g_free(markup); -} - -static void -detach_from_window(void) -{ - GList *list; - - /* find twitter conv window out and detach from that */ - for(list = pidgin_conv_windows_get_list(); list; list = list->next) { - PidginWindow *win = list->data; - PurpleConversation *conv = - pidgin_conv_window_get_active_conversation(win); - gint service = get_service_type(conv); - switch(service) { - case twitter_service: - case wassr_service: - case identica_service: - case jisko_service: - detach_from_conv(conv, NULL); - break; - default: - twitter_debug("unknown service\n"); - break; - } - } -} - -static void -detach_from_conv(PurpleConversation *conv, gpointer null) -{ - PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); - GtkWidget *box, *counter = NULL, *sep = NULL; - - g_signal_handlers_disconnect_by_func(G_OBJECT(gtkconv->entry_buffer), - (GFunc) insert_text_cb, conv); - g_signal_handlers_disconnect_by_func(G_OBJECT(gtkconv->entry_buffer), - (GFunc) delete_text_cb, conv); - - box = gtkconv->toolbar; - - /* remove counter */ - counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter"); - if(counter) { - gtk_container_remove(GTK_CONTAINER(box), counter); - g_object_unref(counter); - g_object_set_data(G_OBJECT(box), PLUGIN_ID "-counter", NULL); - } - - /* remove separator */ - sep = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-sep"); - if(sep) { - gtk_container_remove(GTK_CONTAINER(box), sep); - g_object_unref(sep); - g_object_set_data(G_OBJECT(box), PLUGIN_ID "-sep", NULL); - } - - gtk_widget_queue_draw(pidgin_conv_get_window(gtkconv)->window); -} - -static void -remove_marks_func(gpointer key, gpointer value, gpointer user_data) -{ - icon_data *data = (icon_data *)value; - GtkTextBuffer *text_buffer = (GtkTextBuffer *)user_data; - GList *mark_list = NULL; - GList *current; - - if(!data) - return; - - if(data->request_list) - mark_list = data->request_list; - - /* remove the marks in its GtkTextBuffers */ - current = g_list_first(mark_list); - while(current) { - GtkTextMark *current_mark = current->data; - GtkTextBuffer *current_text_buffer = - gtk_text_mark_get_buffer(current_mark); - GList *next; - - next = g_list_next(current); - - if(!current_text_buffer) - continue; - - if(text_buffer) { - if(current_text_buffer == text_buffer) { - /* the mark will be freed in this function */ - gtk_text_buffer_delete_mark(current_text_buffer, - current_mark); - current->data = NULL; - mark_list = g_list_delete_link(mark_list, current); - } - } - else { - gtk_text_buffer_delete_mark(current_text_buffer, current_mark); - current->data = NULL; - mark_list = g_list_delete_link(mark_list, current); - } - - current = next; - } - - data->request_list = mark_list; -} - -static void -delete_requested_icon_marks(PidginConversation *conv, GHashTable *table) { - GtkTextBuffer *text_buffer = gtk_text_view_get_buffer( - GTK_TEXT_VIEW(conv->imhtml)); - - g_hash_table_foreach(table, - (GHFunc)remove_marks_func, - (gpointer)text_buffer); -} - -static void -attach_to_window(void) -{ - GList *list; - - twitter_debug("called\n"); - - /* find twitter conv window out and attach to that */ - for(list = pidgin_conv_windows_get_list(); list; list = list->next) { - PidginWindow *win = list->data; - PurpleConversation *conv = - pidgin_conv_window_get_active_conversation(win); - gint service = get_service_type(conv); - /* only attach to twitter conversation window */ - switch(service) { - case twitter_service: - case wassr_service: - case identica_service: - case jisko_service: - attach_to_conv(conv, NULL); - break; - default: - twitter_debug("unknown service\n"); - break; - } - } -} - -static void -attach_to_conv(PurpleConversation *conv, gpointer null) -{ - PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); - GtkWidget *box, *sep, *counter, *menus; - GtkIMHtml *imhtml; - - box = gtkconv->toolbar; - imhtml = GTK_IMHTML(gtkconv->imhtml); - - /* Disable widgets that decorate or add link to composing text - * because Twitter cannot receive marked up string. For lean-view - * and wide-view, see pidgin/gtkimhtmltoolbar.c. - */ - menus = g_object_get_data(G_OBJECT(box), "lean-view"); - if(menus) { - gtk_widget_set_sensitive(GTK_WIDGET(menus), FALSE); - } - menus = g_object_get_data(G_OBJECT(box), "wide-view"); - if(menus) { - gtk_widget_set_sensitive(GTK_WIDGET(menus), FALSE); - } - - purple_conversation_set_features( - gtkconv->active_conv, - purple_conversation_get_features(gtkconv->active_conv) & - ~PURPLE_CONNECTION_HTML); - - /* check if the counter is enabled */ - if(!purple_prefs_get_bool(OPT_COUNTER)) - return; - - /* get counter object */ - counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter"); - g_return_if_fail(counter == NULL); - - /* make counter object */ - counter = gtk_label_new(NULL); - gtk_widget_set_name(counter, "counter_label"); - gtk_label_set_text(GTK_LABEL(counter), "0"); - gtk_box_pack_end(GTK_BOX(box), counter, FALSE, FALSE, 0); - gtk_widget_show_all(counter); - g_object_set_data(G_OBJECT(box), PLUGIN_ID "-counter", counter); - - /* make separator object */ - sep = gtk_vseparator_new(); - gtk_box_pack_end(GTK_BOX(box), sep, FALSE, FALSE, 0); - gtk_widget_show_all(sep); - g_object_set_data(G_OBJECT(box), PLUGIN_ID "-sep", sep); - - /* connect to signals */ - g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "insert_text", - G_CALLBACK(insert_text_cb), conv); - g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "delete_range", - G_CALLBACK(delete_text_cb), conv); - - /* redraw window */ - gtk_widget_queue_draw(pidgin_conv_get_window(gtkconv)->window); -} - -static gboolean -is_twitter_account(PurpleAccount *account, const char *name) -{ - const gchar *proto = purple_account_get_protocol_id(account); - - if(g_strstr_len(name, 19, "twitter@twitter.com") && - g_strstr_len(proto, 11, "prpl-jabber")) { - return TRUE; - } - - return FALSE; -} - -static gboolean -is_twitter_conv(PurpleConversation *conv) -{ - g_return_val_if_fail(conv != NULL, FALSE); - - const char *name = purple_conversation_get_name(conv); - PurpleAccount *account = purple_conversation_get_account(conv); - - return is_twitter_account(account, name); -} - -static gboolean -is_wassr_account(PurpleAccount *account, const char *name) -{ - const gchar *proto = purple_account_get_protocol_id(account); - - if(g_strstr_len(name, 18, "wassr-bot@wassr.jp") && - g_strstr_len(proto, 11, "prpl-jabber")) { - return TRUE; - } - - return FALSE; -} - -static gboolean -is_wassr_conv(PurpleConversation *conv) -{ - g_return_val_if_fail(conv != NULL, FALSE); - - const char *name = purple_conversation_get_name(conv); - PurpleAccount *account = purple_conversation_get_account(conv); - - return is_wassr_account(account, name); -} - -static gboolean -is_identica_account(PurpleAccount *account, const char *name) -{ - const gchar *proto = purple_account_get_protocol_id(account); - - if(g_strstr_len(name, 16, "update@identi.ca") && - g_strstr_len(proto, 11, "prpl-jabber")) { - return TRUE; - } - - return FALSE; -} - -static gboolean -is_identica_conv(PurpleConversation *conv) -{ - g_return_val_if_fail(conv != NULL, FALSE); - - const char *name = purple_conversation_get_name(conv); - PurpleAccount *account = purple_conversation_get_account(conv); - - return is_identica_account(account, name); -} - -static gboolean -is_jisko_account(PurpleAccount *account, const char *name) -{ - const gchar *proto = purple_account_get_protocol_id(account); - - if(g_strstr_len(name, 16, "bot@jisko.net") && - g_strstr_len(proto, 11, "prpl-jabber")) { - return TRUE; - } - - return FALSE; -} - -static gboolean -is_jisko_conv(PurpleConversation *conv) -{ - g_return_val_if_fail(conv != NULL, FALSE); - - const char *name = purple_conversation_get_name(conv); - PurpleAccount *account = purple_conversation_get_account(conv); - - return is_jisko_account(account, name); -} - -static gint -get_service_type_by_account(PurpleAccount *account, const char *sender) -{ - gint service = unknown_service; - - g_return_val_if_fail(account != NULL, unknown_service); - g_return_val_if_fail(sender != NULL, unknown_service); - - if(is_twitter_account(account, sender)) - service = twitter_service; - else if(is_wassr_account(account, sender)) - service = wassr_service; - else if(is_identica_account(account, sender)) - service = identica_service; - else if(is_jisko_account(account, sender)) - service = jisko_service; - - return service; -} - -static gint -get_service_type(PurpleConversation *conv) -{ - gint service = unknown_service; - - g_return_val_if_fail(conv != NULL, unknown_service); - - if(is_twitter_conv(conv)) - service = twitter_service; - else if(is_wassr_conv(conv)) - service = wassr_service; - else if(is_identica_conv(conv)) - service = identica_service; - else if(is_jisko_conv(conv)) - service = jisko_service; - - return service; -} - -static void -conv_created_cb(PurpleConversation *conv, gpointer null) -{ - PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); - - twitter_debug("called\n"); - - g_return_if_fail(gtkconv != NULL); - - gint service = get_service_type(conv); - /* only attach to twitter conversation window */ - switch(service) { - case twitter_service: - get_status_with_api((gpointer)conv); - source.id = g_timeout_add_seconds( - purple_prefs_get_int(OPT_API_BASE_GET_INTERVAL), - get_status_with_api, (gpointer)conv); - source.conv = conv; - attach_to_conv(conv, NULL); - break; - case wassr_service: - case identica_service: - case jisko_service: - attach_to_conv(conv, NULL); - break; - default: - twitter_debug("unknown service\n"); - break; - } -} - -static void -deleting_conv_cb(PurpleConversation *conv) -{ - PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); - - twitter_debug("called\n"); - - g_return_if_fail(gtkconv != NULL); - - gint service = get_service_type(conv); - GHashTable *hash = NULL; - - /* only attach to twitter conversation window */ - switch(service) { - case twitter_service: - if(purple_prefs_get_bool(OPT_API_BASE_POST)) { - g_source_remove_by_user_data((gpointer)conv); - source.id = 0; - source.conv = NULL; - } - hash = icon_hash[twitter_service]; - break; - case wassr_service: - hash = icon_hash[wassr_service]; - break; - case identica_service: - hash = icon_hash[identica_service]; - break; - case jisko_service: - hash = icon_hash[jisko_service]; - break; - default: - twitter_debug("unknown service\n"); - break; - } - - if(hash) - delete_requested_icon_marks(gtkconv, hash); -} - -static void -apply_filter(gchar **sender, gchar **buffer, PurpleMessageFlags *flags, int service) -{ - GMatchInfo *match_info; - const gchar *list = NULL; - gchar *screen_name = NULL; - gchar **candidates = NULL, **candidate = NULL; - - g_return_if_fail(*sender != NULL); - g_return_if_fail(*buffer != NULL); - - gchar *plain = strip_html_markup(*buffer); - - switch(service) { - case twitter_service: - default: - list = purple_prefs_get_string(OPT_FILTER_TWITTER); - screen_name = g_strdup_printf("@%s", purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER)); - break; - case wassr_service: - list = purple_prefs_get_string(OPT_FILTER_WASSR); - screen_name = g_strdup_printf("@%s", purple_prefs_get_string(OPT_SCREEN_NAME_WASSR)); - break; - case identica_service: - list = purple_prefs_get_string(OPT_FILTER_IDENTICA); - screen_name = g_strdup_printf("@%s", purple_prefs_get_string(OPT_SCREEN_NAME_IDENTICA)); - break; - case jisko_service: - list = purple_prefs_get_string(OPT_FILTER_JISKO); - screen_name = g_strdup_printf("@%s", purple_prefs_get_string(OPT_SCREEN_NAME_JISKO)); - break; - } - g_return_if_fail(list != NULL); - if(strstr(list, DEFAULT_LIST)) - return; - - /* find @myself */ - if(purple_prefs_get_bool(OPT_FILTER_EXCLUDE_REPLY) && - strstr(plain, screen_name)) { - g_free(plain); - g_free(screen_name); - return; - } - else - g_free(screen_name); - - candidates = g_strsplit_set(list, " ,:;", 0); - g_return_if_fail(candidates != NULL); - - g_regex_match(regp[SENDER], plain, 0, &match_info); - while(g_match_info_matches(match_info)) { - gchar *user = NULL; - user = g_match_info_fetch(match_info, 2); - twitter_debug("user = %s\n", user); - - for(candidate = candidates; *candidate; candidate++) { - if(!strcmp(*candidate, "")) - continue; - twitter_debug("candidate = %s\n", *candidate); - if(!strcmp(user, *candidate)) { - twitter_debug("match. filter %s\n", user); - /* pidgin should handle this flag properly --yaz */ -// *flags |= PURPLE_MESSAGE_INVISIBLE; - - /* temporal workaround */ - g_free(*sender); *sender = NULL; - g_free(*buffer); *buffer = NULL; - break; - } - } - - g_free(user); - g_match_info_next(match_info, NULL); - } - - g_free(plain); - g_strfreev(candidates); - g_match_info_free(match_info); -} - - -static gboolean -receiving_im_cb(PurpleAccount *account, char **sender, char **buffer, - PurpleConversation *conv, PurpleMessageFlags *flags, void *data) -{ - twitter_debug("called\n"); - twitter_debug("buffer = %s suppress_oops = %d\n", *buffer, suppress_oops); - - gint service; - - service = get_service_type_by_account(account, *sender); - twitter_debug("service = %d\n", service); - -#ifdef _WIN32 - /* suppress notification of incoming messages. */ - if(service != unknown_service && - purple_prefs_get_bool(OPT_PREVENT_NOTIFICATION)) { - if(!blink_modified) { - blink_modified = TRUE; - blink_state = purple_prefs_get_bool(OPT_PIDGIN_BLINK_IM); - purple_prefs_set_bool(OPT_PIDGIN_BLINK_IM, FALSE); - } - } - else { - if(blink_modified) { - purple_prefs_set_bool(OPT_PIDGIN_BLINK_IM, blink_state); - blink_modified = FALSE; - } - } -#endif - - if(service == wassr_service) { - gchar *stripped = strip_html_markup(*buffer); - /* suppress annoying completion message from wassr */ - if(strstr(*buffer, "チャンネル投稿完了:")) { - twitter_debug("clearing channel parrot message\n"); - g_free(*sender); *sender = NULL; - g_free(*buffer); *buffer = NULL; - } - /* discard parrot message */ - else { - GList *current = g_list_first(wassr_parrot_list); - while(current) { - GList *next = g_list_next(current); - - if(strstr(stripped, current->data)) { - twitter_debug("parrot clearing: buf = %s post = %s\n", - *buffer, (char *)current->data); - g_free(*sender); *sender = NULL; - g_free(*buffer); *buffer = NULL; - g_free(current->data); - current->data = NULL; - wassr_parrot_list = - g_list_delete_link(wassr_parrot_list, current); - break; - } - - current = next; - } - } - g_free(stripped); - } - - if(service == identica_service) { - /* discard parrot message */ - gchar *stripped = strip_html_markup(*buffer); - GList *current = g_list_first(identica_parrot_list); - while(current) { - GList *next = g_list_next(current); - - if(strstr(stripped, current->data)) { - twitter_debug("identica parrot clearing: buf = %s post = %s\n", - *buffer, (char *)current->data); - g_free(*sender); *sender = NULL; - g_free(*buffer); *buffer = NULL; - g_free(current->data); - current->data = NULL; - identica_parrot_list = - g_list_delete_link(identica_parrot_list, current); - break; - } - - current = next; - } - g_free(stripped); - } - - /* filtering */ - if(purple_prefs_get_bool(OPT_FILTER)) { - apply_filter(sender, buffer, flags, service); - } - - /* return here if it is not twitter */ - if(service != twitter_service) { - return FALSE; - } - - /* if we use api, discard all incoming IM messages. */ - if(purple_prefs_get_bool(OPT_API_BASE_POST)) { - g_free(*sender); *sender = NULL; - g_free(*buffer); *buffer = NULL; - } - - if(!suppress_oops || !purple_prefs_get_bool(OPT_SUPPRESS_OOPS)) - return FALSE; - - if(strstr(*buffer, OOPS_MESSAGE)) { - twitter_debug("clearing sender and buffer\n"); - g_free(*sender); *sender = NULL; - g_free(*buffer); *buffer = NULL; - suppress_oops = FALSE; - } - return FALSE; -} - -static void -insert_icon_at_mark(GtkTextMark *requested_mark, gpointer user_data) -{ - got_icon_data *gotdata = (got_icon_data *)user_data; - - gchar *user_name = gotdata->user_name; - gint service = gotdata->service; - - GList *win_list; - GtkIMHtml *target_imhtml = NULL; - GtkTextBuffer *target_buffer = NULL; - GtkTextIter insertion_point; - icon_data *data = NULL; - GHashTable *hash = NULL; - - twitter_debug("called: service = %d\n", service); - - /* find the conversation that contains the mark */ - for(win_list = pidgin_conv_windows_get_list(); win_list; - win_list = win_list->next) { - PidginWindow *win = win_list->data; - GList *conv_list; - - for(conv_list = pidgin_conv_window_get_gtkconvs(win); conv_list; - conv_list = conv_list->next) { - PidginConversation *conv = conv_list->data; - PurpleConversation *purple_conv = conv->active_conv; - - gint service = get_service_type(purple_conv); - - if(service != unknown_service) { - GtkIMHtml *current_imhtml = GTK_IMHTML(conv->imhtml); - GtkTextBuffer *current_buffer = gtk_text_view_get_buffer( - GTK_TEXT_VIEW(current_imhtml)); - - if(current_buffer == gtk_text_mark_get_buffer(requested_mark)) { - target_imhtml = current_imhtml; - target_buffer = current_buffer; - break; - } - } - } - } - - if(!(target_imhtml && target_buffer)) { - return; - } - - /* insert icon to the mark */ - gtk_text_buffer_get_iter_at_mark(target_buffer, - &insertion_point, requested_mark); - - /* insert icon */ - switch(service) { - case twitter_service: - hash = icon_hash[twitter_service]; - break; - case wassr_service: - hash = icon_hash[wassr_service]; - break; - case identica_service: - hash = icon_hash[identica_service]; - break; - case jisko_service: - hash = icon_hash[jisko_service]; - break; - default: - twitter_debug("unknown service\n"); - } - - if(hash) - data = (icon_data *)g_hash_table_lookup(hash, user_name); - - - /* in this function, we put an icon for pending marks. we should - * not invalidate the icon here, otherwise it may result in - * thrashing. --yaz */ - - if(!data || !data->pixbuf) { - return; - } - - /* insert icon actually */ - if(purple_prefs_get_bool(OPT_SHOW_ICON)) { - gtk_text_buffer_insert_pixbuf(target_buffer, - &insertion_point, - data->pixbuf); - data->use_count++; - } - gtk_text_buffer_delete_mark(target_buffer, requested_mark); - requested_mark = NULL; -} - -static void -insert_requested_icon(const gchar *user_name, gint service) -{ - icon_data *data = NULL; - GList *mark_list = NULL; - GHashTable *hash = NULL; - - twitter_debug("called\n"); - - switch(service) { - case twitter_service: - hash = icon_hash[twitter_service]; - break; - case wassr_service: - hash = icon_hash[wassr_service]; - break; - case identica_service: - hash = icon_hash[identica_service]; - break; - case jisko_service: - hash = icon_hash[jisko_service]; - break; - default: - twitter_debug("unknown service\n"); - break; - } - - if(hash) - data = (icon_data *)g_hash_table_lookup(hash, user_name); - - if(!data) - return; - - mark_list = data->request_list; - - got_icon_data *gotdata = g_new0(got_icon_data, 1); - gotdata->user_name = g_strdup(user_name); - gotdata->service = service; - - twitter_debug("about to insert icon for pending requests\n"); - - if(mark_list) { - g_list_foreach(mark_list, (GFunc) insert_icon_at_mark, gotdata); - mark_list = g_list_remove_all(mark_list, NULL); - g_list_free(mark_list); - data->request_list = NULL; - } - - g_free(gotdata->user_name); - g_free(gotdata); -} - -/* this function will be called when profile page has been retrieved */ -static void -got_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, - const gchar *url_text, gsize len, const gchar *error_message) -{ - got_icon_data *gotdata = (got_icon_data *)user_data; - gchar *user_name = gotdata->user_name; - gint service = gotdata->service; - GMatchInfo *match_info = NULL; - icon_data *data = NULL; - gchar *url = NULL; - gint regp_id = -1; - - if(service == twitter_service) { - data = (icon_data *)g_hash_table_lookup( - icon_hash[twitter_service], user_name); - regp_id = IMAGE_TWITTER; - } - else if(service == wassr_service) { - data = (icon_data *)g_hash_table_lookup( - icon_hash[wassr_service], user_name); - regp_id = IMAGE_WASSR; - } - else if(service == identica_service) { - data = (icon_data *)g_hash_table_lookup( - icon_hash[identica_service], user_name); - regp_id = IMAGE_IDENTICA; - } - else if(service == jisko_service) { - data = (icon_data *)g_hash_table_lookup( - icon_hash[jisko_service], user_name); - regp_id = IMAGE_JISKO; - } - - if(!url_text) { - if(data) { - data->requested = FALSE; - data->fetch_data = NULL; - } - g_free(gotdata->user_name); - g_free(gotdata); - return; - } - - /* setup image url */ /* xxx need simplify --yaz */ - g_regex_match(regp[regp_id], url_text, 0, &match_info); - if(!g_match_info_matches(match_info)) { - g_match_info_free(match_info); - - if(service == twitter_service) { - twitter_debug("fall back to twitter default icon\n"); - url = g_strdup(TWITTER_DEFAULT_ICON_URL); - } - else if(service == jisko_service) { - twitter_debug("fall back to jisko default icon\n"); - url = g_strdup(JISKO_DEFAULT_ICON_URL); - } - else { - twitter_debug("no image url found\n"); - if(data) { - data->requested = FALSE; - data->fetch_data = NULL; - } - g_free(gotdata->user_name); - g_free(gotdata); - return; - } - } - else { - url = g_match_info_fetch(match_info, 1); - g_match_info_free(match_info); - } - - /* find out basename */ - gchar *slash = strrchr(url, '/'); - *slash = '\0'; - - gchar *lower = g_ascii_strdown(slash+1, -1); - - if(strstr(lower, ".png")) - data->img_type = "png"; - else if(strstr(lower, ".gif")) - data->img_type = "gif"; - else if(strstr(lower, ".jpg") || strstr(lower, ".jpeg")) - data->img_type = "jpg"; - - g_free(lower); - - gchar *tmp; - /* url encode basename. twitter needs this. */ - if(service == twitter_service) - tmp = g_strdup_printf("%s/%s", url, - purple_url_encode(slash+1)); - else if(service == wassr_service) { - gchar *tmp0 = NULL; - tmp0 = g_regex_replace(regp[SIZE_128_WASSR], slash+1, - -1, 0, ".64.", 0, NULL); - tmp = g_strdup_printf("http://wassr.jp%s/%s", url, - tmp0 ? tmp0 : slash+1); - g_free(tmp0); - } - else { - tmp = g_strdup_printf("%s/%s", url, slash+1); - } - - g_free(url); - url = tmp; - - /* if requesting icon url is the same as old, return. */ - if(url && data->icon_url && !strcmp(data->icon_url, url)) { - twitter_debug("old url = %s new url = %s\n", data->icon_url, url); - data->requested = FALSE; - data->fetch_data = NULL; - g_free(url); - return; - } - - if(data && data->pixbuf) { - gdk_pixbuf_unref(data->pixbuf); - data->pixbuf = NULL; - } - - g_free(data->icon_url); - data->icon_url = g_strdup(url); - - data->use_count = 0; - data->mtime = time(NULL); /* xxx is there a better way? */ - - twitter_debug("requested url=%s\n", url); - - /* request fetch image */ - if(url) { - /* reuse gotdata. just pass given one */ - /* gotdata will be released in got_icon_cb */ - data->fetch_data = purple_util_fetch_url(url, - TRUE, NULL, TRUE, - got_icon_cb, gotdata); - twitter_debug("request %s's icon\n", user_name); - g_free(url); - } -} - -static GdkPixbuf * -make_scaled_pixbuf(const gchar *url_text, gsize len) -{ - /* make pixbuf */ - GdkPixbufLoader *loader; - GdkPixbuf *src = NULL, *dest = NULL; - gint size; - - g_return_val_if_fail(url_text != NULL, NULL); - g_return_val_if_fail(len > 0, NULL); - - loader = gdk_pixbuf_loader_new(); - gdk_pixbuf_loader_write(loader, (guchar *)url_text, len, NULL); - gdk_pixbuf_loader_close(loader, NULL); - - src = gdk_pixbuf_loader_get_pixbuf(loader); - if(!src) - return NULL; - - size = purple_prefs_get_int(OPT_ICON_SIZE); - if(size == 0) - size = DEFAULT_ICON_SIZE; - - dest = gdk_pixbuf_scale_simple(src, size, size, GDK_INTERP_HYPER); - gdk_pixbuf_unref(src); - - return dest; -} - -static gchar *ext_list[] = { - "png", - "gif", - "jpg", - NULL -}; - -/* this function will be called when requested icon has been retrieved */ -static void -got_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, - const gchar *url_text, gsize len, const gchar *error_message) -{ - got_icon_data *gotdata = (got_icon_data *)user_data; - gchar *user_name = gotdata->user_name; - gint service = gotdata->service; - - icon_data *data = NULL; - GHashTable *hash = NULL; - GdkPixbuf *pixbuf = NULL; - const gchar *dirname = NULL; - - twitter_debug("called: service = %d\n", service); - - switch(service) { - case twitter_service: - hash = icon_hash[twitter_service]; - break; - case wassr_service: - hash = icon_hash[wassr_service]; - break; - case identica_service: - hash = icon_hash[identica_service]; - break; - case jisko_service: - hash = icon_hash[jisko_service]; - break; - default: - twitter_debug("unknown service\n"); - } - - if(hash) - data = (icon_data *)g_hash_table_lookup(hash, user_name); - - /* return if download failed */ - if(!url_text) { - twitter_debug("downloading %s's icon failed : %s\n", - user_name, error_message); - if(data) - data->requested = FALSE; - - goto fin_got_icon_cb; - } - - if(data) { - /* remove download request */ - data->requested = FALSE; - data->fetch_data = NULL; - - /* return if user's icon had been downloaded */ - if(data->pixbuf) { - twitter_debug("%s's icon has already been downloaded\n", - user_name); - - goto fin_got_icon_cb; - } - } - - pixbuf = make_scaled_pixbuf(url_text, len); - - if(!pixbuf) - goto fin_got_icon_cb; - - - if(!data) { - twitter_debug("allocate icon_data (shouldn't be called)\n"); - data = g_new0(icon_data, 1); - } - - data->pixbuf = pixbuf; - - twitter_debug("new icon pixbuf = %p size = %d\n", - pixbuf, - gdk_pixbuf_get_rowstride(pixbuf) * - gdk_pixbuf_get_height(pixbuf)); - - if(hash) - g_hash_table_insert(hash, g_strdup(user_name), data); - - dirname = purple_prefs_get_string(OPT_ICON_DIR); - - /* store retrieved image to a file in icon dir */ - if(ensure_path_exists(dirname)) { - gchar *filename = NULL; - gchar *path = NULL; - const gchar *suffix = NULL; - gchar **extp; - - switch(service) { - case twitter_service: - suffix = "twitter"; - break; - case wassr_service: - suffix = "wassr"; - break; - case identica_service: - suffix = "identica"; - break; - case jisko_service: - suffix = "jisko"; - break; - default: - twitter_debug("unknown service\n"); - break; - } - - /* remove old file first */ - for(extp = ext_list; *extp; extp++) { - filename = g_strdup_printf("%s_%s.%s", - user_name, suffix, *extp); - path = g_build_filename(dirname, filename, NULL); - g_remove(path); - - g_free(filename); - g_free(path); - } - - /* setup path */ - filename = g_strdup_printf("%s_%s.%s", - user_name, suffix, data->img_type); - - path = g_build_filename(dirname, filename, NULL); - g_free(filename); filename = NULL; - - g_file_set_contents(path, url_text, len, NULL); - g_free(path); path = NULL; - - data->mtime = time(NULL); - } - - twitter_debug("Downloading %s's icon has been complete.\n", - user_name); - - /* Insert the icon to messages that had been received. */ - insert_requested_icon(user_name, service); - -fin_got_icon_cb: - g_free(gotdata->user_name); - g_free(gotdata); -} - -static void -request_icon(const char *user_name, gint service, gboolean renew) -{ - gchar *url = NULL; - - /* look local icon cache for the requested icon */ - gchar *path = NULL; - icon_data *data = NULL; - GHashTable *hash = NULL; - const gchar *suffix = NULL; - - switch(service) { - case twitter_service: - hash = icon_hash[twitter_service]; - suffix = "twitter"; - break; - case wassr_service: - hash = icon_hash[wassr_service]; - suffix = "wassr"; - break; - case identica_service: - suffix = "identica"; - hash = icon_hash[identica_service]; - break; - case jisko_service: - suffix = "jisko"; - hash = icon_hash[jisko_service]; - break; - default: - twitter_debug("unknown service\n"); - break; - } - - if(!hash) - return; - - /* since this function is called after mark_icon_for_user(), data - * must exist here. */ - data = (icon_data *)g_hash_table_lookup(hash, user_name); - - /* if img has been registerd, just return */ - if(data && data->pixbuf && !renew) - return; - - /* check if saved file exists */ - if(suffix && !renew) { - gchar *filename = NULL; - gchar **extp; - - for(extp = ext_list; *extp; extp++) { - filename = g_strdup_printf("%s_%s.%s", user_name, suffix, *extp); - path = g_build_filename(purple_prefs_get_string(OPT_ICON_DIR), - filename, NULL); - g_free(filename); - - twitter_debug("path = %s\n", path); - - /* build image from file, if file exists */ - if(g_file_test(path, G_FILE_TEST_EXISTS)) { - gchar *imgdata = NULL; - size_t len; - GError *err = NULL; - GdkPixbuf *pixbuf = NULL; - struct stat buf; - - if (!g_file_get_contents(path, &imgdata, &len, &err)) { - twitter_debug("Error reading %s: %s\n", - path, err->message); - g_error_free(err); - } - - if(stat(path, &buf)) - data->mtime = buf.st_mtime; - - pixbuf = make_scaled_pixbuf(imgdata, len); - g_free(imgdata); - - if(pixbuf) { - data->pixbuf = pixbuf; - - twitter_debug("new icon pixbuf = %p size = %d\n", - pixbuf, - gdk_pixbuf_get_rowstride(pixbuf) * - gdk_pixbuf_get_height(pixbuf)); - - data->img_type = *extp; - - twitter_debug("icon data has been loaded from file\n"); - insert_requested_icon(user_name, service); - } - - g_free(path); - return; - } - - g_free(path); - - } /* for */ - } /* suffix */ - - /* Return if user's icon has been requested already. */ - if(data->requested) - return; - else - data->requested = TRUE; - - /* Create the URL for an user's icon. */ - switch(service) { - case twitter_service: - url = g_strdup_printf("http://twitter.com/%s", user_name); - break; - case wassr_service: - url = g_strdup_printf("http://wassr.jp/user/%s", user_name); - break; - case identica_service: - url = g_strdup_printf("http://identi.ca/%s", user_name); - break; - case jisko_service: - url = g_strdup_printf("http://jisko.net/%s", user_name); - break; - default: - twitter_debug("unknown service\n"); - break; - } - - if(url) { - got_icon_data *gotdata = g_new0(got_icon_data, 1); - gotdata->user_name = g_strdup(user_name); - gotdata->service = service; - - /* gotdata will be released in got_icon_cb */ - if(service == twitter_service || - service == wassr_service || - service == identica_service || - service == jisko_service) { - data->fetch_data = purple_util_fetch_url(url, TRUE, NULL, TRUE, - got_page_cb, gotdata); - } - else { - data->fetch_data = purple_util_fetch_url(url, TRUE, NULL, TRUE, - got_icon_cb, gotdata); - } - g_free(url); url = NULL; - - twitter_debug("request %s's icon\n", user_name); - } -} - -static void -mark_icon_for_user(GtkTextMark *mark, const gchar *user_name, gint service) -{ - icon_data *data = NULL; - GHashTable *hash = NULL; - - twitter_debug("called\n"); - - switch(service) { - case twitter_service: - hash = icon_hash[twitter_service]; - break; - case wassr_service: - hash = icon_hash[wassr_service]; - break; - case identica_service: - hash = icon_hash[identica_service]; - break; - case jisko_service: - hash = icon_hash[jisko_service]; - break; - default: - twitter_debug("unknown service\n"); - break; - } - - if(hash) - data = (icon_data *)g_hash_table_lookup(hash, user_name); - - /* proper place to allocate icon_data */ - if(!data) { - data = g_new0(icon_data, 1); - g_hash_table_insert(hash, g_strdup(user_name), data); - } - - data->request_list = g_list_prepend(data->request_list, mark); -} - -static gboolean -displaying_im_cb(PurpleAccount *account, const char *who, char **message, - PurpleConversation *conv, PurpleMessageFlags flags, - void *unused) -{ - GtkIMHtml *imhtml; - GtkTextBuffer *text_buffer; - gint service = get_service_type(conv); - gint linenumber = 0; - - twitter_debug("called\n"); - - if(service == unknown_service) { - twitter_debug("neither twitter nor wassr conv\n"); - return FALSE; - } - - /* get text buffer */ - imhtml = GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml); - text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(imhtml)); - - /* store number of lines */ - linenumber = gtk_text_buffer_get_line_count(text_buffer); - g_hash_table_insert(conv_hash, conv, GINT_TO_POINTER(linenumber)); - twitter_debug("conv = %p linenumber = %d\n", conv, linenumber); - - return FALSE; -} - -static void -displayed_im_cb(PurpleAccount *account, const char *who, char *message, - PurpleConversation *conv, PurpleMessageFlags flags) -{ - GMatchInfo *match_info = NULL; - gchar *user_name = NULL; - GtkIMHtml *imhtml; - GtkTextBuffer *text_buffer; - GtkTextIter insertion_point; - gint service = get_service_type(conv); - icon_data *data = NULL; - gint linenumber; - GHashTable *hash = NULL; - gboolean renew = FALSE; - - twitter_debug("called\n"); - - if(service == unknown_service) { - twitter_debug("unknown service\n"); - return; - } - - /* get user's name */ - g_regex_match(regp[USER], message, 0, &match_info); - if(!g_match_info_matches(match_info)) { - twitter_debug("message was not matched : %s\n", message); - g_match_info_free(match_info); - return; - } - - user_name = g_match_info_fetch(match_info, 1); - g_match_info_free(match_info); - - /* insert icon */ - imhtml = GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml); - text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(imhtml)); - - /* get GtkTextIter in the target line */ - linenumber = GPOINTER_TO_INT(g_hash_table_lookup(conv_hash, conv)); - gtk_text_buffer_get_iter_at_line(text_buffer, - &insertion_point, - linenumber); - - switch(service) { - case twitter_service: - hash = icon_hash[twitter_service]; - break; - case wassr_service: - hash = icon_hash[wassr_service]; - break; - case identica_service: - hash = icon_hash[identica_service]; - break; - case jisko_service: - hash = icon_hash[jisko_service]; - break; - default: - twitter_debug("unknown service\n"); - break; - } - - if(hash) - data = g_hash_table_lookup(hash, user_name); - - if(data) { - /* check validity of icon */ - int count_thres = purple_prefs_get_int(OPT_ICON_MAX_COUNT); - int days_thres = DAYS_TO_SECONDS( - purple_prefs_get_int(OPT_ICON_MAX_DAYS)); - - if(data->use_count > count_thres || - (data->mtime && ((time(NULL) - data->mtime)) > days_thres)) { - twitter_debug("count=%d mtime=%d\n", - data->use_count, (int)(data->mtime)); - renew = TRUE; - request_icon(user_name, service, renew); - } - } - - /* if we don't have the icon for this user, put a mark instead and - * request the icon */ - if(!data || !data->pixbuf) { - twitter_debug("%s's icon is not in memory.\n", user_name); - mark_icon_for_user(gtk_text_buffer_create_mark( - text_buffer, NULL, &insertion_point, FALSE), - user_name, service); - /* request to attach icon to the buffer */ - request_icon(user_name, service, renew); - g_free(user_name); user_name = NULL; - return; - } - - /* if we have icon for this user, insert icon immediately */ - if(purple_prefs_get_bool(OPT_SHOW_ICON)) { - gtk_text_buffer_insert_pixbuf(text_buffer, - &insertion_point, - data->pixbuf); - data->use_count++; - } - g_free(user_name); user_name = NULL; - - twitter_debug("reach end of function\n"); -} - - -static void -signed_on_cb(PurpleConnection *gc) -{ - PurpleBuddyList *list = purple_get_blist(); - PurpleBlistNode *gnode, *cnode, *bnode; - PurpleBuddy *b; - - twitter_debug("called\n"); - - if(!purple_prefs_get_bool(OPT_API_BASE_POST)) - return; - - if (!list) - return; - - twitter_debug("scan list\n"); - - for (gnode = list->root; gnode; gnode = gnode->next) { - if(!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - - for(cnode = gnode->child; cnode; cnode = cnode->next) { - - if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; - - for(bnode = cnode->child; bnode; bnode = bnode->next) { - if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - - b = (PurpleBuddy *)bnode; - - if(!PURPLE_BUDDY_IS_ONLINE(b)) { - const char *name; - PurpleAccount *account; - name = purple_buddy_get_name(b); - account = purple_buddy_get_account(b); - if (is_twitter_account(account, name)) { - PurpleConversation *gconv; - gconv = purple_find_conversation_with_account( - PURPLE_CONV_TYPE_IM, name, account); - if (!gconv) { - gconv = purple_conversation_new( - PURPLE_CONV_TYPE_IM, account, name); - } - } - } - } - } - } -} - -static void -api_base_post_cb(const char *name, PurplePrefType type, gconstpointer value, - gpointer data) -{ - signed_on_cb(NULL); - get_status_with_api((gpointer)(source.conv)); -} - -static gboolean -load_plugin(PurplePlugin *plugin) -{ - int i; - - /* connect to signal */ - purple_signal_connect(purple_conversations_get_handle(), "writing-im-msg", - plugin, PURPLE_CALLBACK(writing_im_cb), NULL); - purple_signal_connect(purple_conversations_get_handle(), "sending-im-msg", - plugin, PURPLE_CALLBACK(sending_im_cb), NULL); - purple_signal_connect(purple_conversations_get_handle(), - "conversation-created", - plugin, PURPLE_CALLBACK(conv_created_cb), NULL); - purple_signal_connect(purple_conversations_get_handle(), "receiving-im-msg", - plugin, PURPLE_CALLBACK(receiving_im_cb), NULL); - purple_signal_connect(pidgin_conversations_get_handle(), "displaying-im-msg", - plugin, PURPLE_CALLBACK(displaying_im_cb), NULL); - - purple_signal_connect(pidgin_conversations_get_handle(), "displayed-im-msg", - plugin, PURPLE_CALLBACK(displayed_im_cb), NULL); - purple_signal_connect(purple_conversations_get_handle(), - "deleting-conversation", - plugin, PURPLE_CALLBACK(deleting_conv_cb), NULL); - purple_signal_connect(purple_connections_get_handle(), "signed-on", - plugin, PURPLE_CALLBACK(signed_on_cb), NULL); - - - /* compile regex */ - regp[RECIPIENT] = g_regex_new(P_RECIPIENT, 0, 0, NULL); - regp[SENDER] = g_regex_new(P_SENDER, 0, 0, NULL); - regp[COMMAND] = g_regex_new(P_COMMAND, G_REGEX_RAW, 0, NULL); - regp[PSEUDO] = g_regex_new(P_PSEUDO, G_REGEX_RAW, 0, NULL); - regp[USER] = g_regex_new(P_USER, 0, 0, NULL); - regp[CHANNEL_WASSR] = g_regex_new(P_CHANNEL, 0, 0, NULL); - regp[TAG_IDENTICA] = g_regex_new(P_TAG_IDENTICA, 0, 0, NULL); - regp[IMAGE_TWITTER] = g_regex_new(P_IMAGE_TWITTER, 0, 0, NULL); - regp[IMAGE_WASSR] = g_regex_new(P_IMAGE_WASSR, 0, 0, NULL); - regp[IMAGE_IDENTICA] = g_regex_new(P_IMAGE_IDENTICA, 0, 0, NULL); - regp[IMAGE_JISKO] = g_regex_new(P_IMAGE_JISKO, 0, 0, NULL); - regp[SIZE_128_WASSR] = g_regex_new(P_SIZE_128_WASSR, 0, 0, NULL); - regp[EXCESS_LF] = g_regex_new(P_EXCESS_LF, 0, 0, NULL); - - for(i = twitter_service; i < NUM_SERVICES; i++) { - icon_hash[i] = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, NULL); - } - - conv_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, - NULL, NULL); - - - /* attach counter to the existing twitter window */ - if(purple_prefs_get_bool(OPT_COUNTER)) { - attach_to_window(); - } - - return TRUE; -} - -static void -cancel_fetch_func(gpointer key, gpointer value, gpointer user_data) -{ - icon_data *data = (icon_data *)value; - - if(!data) - return; - - if(data->requested) { - purple_util_fetch_url_cancel(data->fetch_data); - data->fetch_data = NULL; - data->requested = FALSE; - } - - if(data->request_list) { - twitter_debug("somehow, request_list != NULL\n"); - } -} - -static void -cleanup_hash_entry_func(gpointer key, gpointer value, gpointer user_data) -{ - remove_marks_func(key, value, user_data); - cancel_fetch_func(key, value, user_data); -} - -static gboolean -unload_plugin(PurplePlugin *plugin) -{ - int i; - GList *current; - - twitter_debug("called\n"); - - /* disconnect from signal */ - purple_signal_disconnect(purple_conversations_get_handle(), - "writing-im-msg", - plugin, PURPLE_CALLBACK(writing_im_cb)); - purple_signal_disconnect(purple_conversations_get_handle(), - "sending-im-msg", - plugin, PURPLE_CALLBACK(sending_im_cb)); - purple_signal_disconnect(purple_conversations_get_handle(), - "conversation-created", - plugin, PURPLE_CALLBACK(conv_created_cb)); - purple_signal_disconnect(pidgin_conversations_get_handle(), - "displaying-im-msg", - plugin, PURPLE_CALLBACK(displaying_im_cb)); - purple_signal_disconnect(pidgin_conversations_get_handle(), - "displayed-im-msg", - plugin, PURPLE_CALLBACK(displayed_im_cb)); - purple_signal_disconnect(purple_conversations_get_handle(), - "receiving-im-msg", - plugin, PURPLE_CALLBACK(receiving_im_cb)); - purple_signal_disconnect(purple_conversations_get_handle(), - "deleting-conversation", - plugin, PURPLE_CALLBACK(deleting_conv_cb)); - purple_signal_disconnect(purple_connections_get_handle(), - "signed-on", - plugin, PURPLE_CALLBACK(signed_on_cb)); - - /* unreference regp */ - for(i = 0; i < NUM_REGPS; i++) { - g_regex_unref(regp[i]); - } - - /* remove mark list in each hash entry */ - /* cancel request that has not been finished yet */ - for(i = twitter_service; i < NUM_SERVICES; i++) { - /* delete mark list and stop requeset for each hash table */ - g_hash_table_foreach(icon_hash[i], - (GHFunc)cleanup_hash_entry_func, NULL); - /* destroy hash table for icon_data */ - g_hash_table_destroy(icon_hash[i]); - } - - g_hash_table_destroy(conv_hash); - - /* detach from twitter window */ - detach_from_window(); - - /* free wassr_parrot_list */ - current = g_list_first(wassr_parrot_list); - while(current) { - GList *next; - - next = g_list_next(current); - g_free(current->data); - wassr_parrot_list = - g_list_delete_link(wassr_parrot_list, current); - - current = next; - } - g_list_free(wassr_parrot_list); - wassr_parrot_list = NULL; - - /* free identica_parot_list */ - current = g_list_first(identica_parrot_list); - while(current) { - GList *next; - - next = g_list_next(current); - g_free(current->data); - identica_parrot_list = - g_list_delete_link(identica_parrot_list, current); - - current = next; - } - g_list_free(identica_parrot_list); - identica_parrot_list = NULL; - - return TRUE; -} - -static void -counter_prefs_cb(const char *name, PurplePrefType type, - gconstpointer val, gpointer data) -{ - gboolean enabled = purple_prefs_get_bool(OPT_COUNTER); - - if(enabled) - attach_to_window(); - else - detach_from_window(); -} - -static void -invalidate_icon_data_func(gpointer key, gpointer value, gpointer user_data) -{ - icon_data *data = (icon_data *)value; - - g_return_if_fail(data != NULL); - - g_object_unref(data->pixbuf); - data->pixbuf = NULL; -} - -static void -icon_size_prefs_cb(const char *name, PurplePrefType type, - gconstpointer val, gpointer data) -{ - int i; - - /* invalidate icon cache */ - for(i = twitter_service; i < NUM_SERVICES; i++) { - g_hash_table_foreach(icon_hash[i], - (GHFunc)invalidate_icon_data_func, NULL); - } -} - -static void -interval_prefs_cb(const char *name, PurplePrefType type, - gconstpointer val, gpointer data) -{ - /* remove idle func */ - g_source_remove_by_user_data((gpointer)(source.conv)); - - /* add idle func */ - if(purple_prefs_get_bool(OPT_API_BASE_POST)) { - source.id = g_timeout_add_seconds( - purple_prefs_get_int(OPT_API_BASE_GET_INTERVAL), - get_status_with_api, (gpointer)(source.conv)); - } -} - -static void -text_changed_cb(gpointer *data) -{ - const gchar *text; - gchar *pref = (gchar *)g_object_get_data(G_OBJECT(data), "pref"); - text = gtk_entry_get_text(GTK_ENTRY(data)); - purple_prefs_set_string(pref, text); -} - -static void -bool_toggled_cb(gpointer *data) -{ - gchar *pref = (gchar *)g_object_get_data(G_OBJECT(data), "pref"); - gboolean value = purple_prefs_get_bool(pref); - purple_prefs_set_bool(pref, !value); -} - -static void -spin_changed_cb(gpointer *data) -{ - gchar *pref = (gchar *)g_object_get_data(G_OBJECT(data), "pref"); - - twitter_debug("called\n"); - - purple_prefs_set_int(pref, - gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(data))); -} - -static void -combo_changed_cb(gpointer *data) -{ - gint position; - gchar *pref = (gchar *)g_object_get_data(G_OBJECT(data), "pref"); - position = gtk_combo_box_get_active(GTK_COMBO_BOX(data)); - purple_prefs_set_int(pref, position); -} - -static void -disconnect_prefs_cb(GtkObject *object, gpointer data) -{ - PurplePlugin *plugin = (PurplePlugin *)data; - - purple_prefs_disconnect_by_handle(plugin); -} - -static GtkWidget * -prefs_get_frame(PurplePlugin *plugin) -{ - GtkBuilder *builder; - GError *err = NULL; - gchar *filename; - GtkWidget *window, *notebook, *e; - const gchar *text; - GtkSpinButton *spin; - GtkObject *adjust; - gint value; -#ifdef _WIN32 - extern char binary_prefs_ui_start[]; - extern char binary_prefs_ui_size[]; -#endif - - builder = gtk_builder_new(); - -#ifdef _WIN32 - gtk_builder_add_from_string(builder, binary_prefs_ui_start, - (int)binary_prefs_ui_size, NULL); -#else - filename = g_build_filename(DATADIR, - "pidgin-twitter", "prefs.ui", NULL); - gtk_builder_add_from_file(builder, filename, &err); -#endif - - if(err) { - twitter_debug("%s\n", err->message); - g_free(filename); - return NULL; - } - - g_free(filename); - - gtk_builder_connect_signals(builder, NULL); - - window = GTK_WIDGET(gtk_builder_get_object(builder, "prefswindow")); - notebook = GTK_WIDGET(gtk_builder_get_object(builder, "prefsnotebook")); - - gtk_container_remove(GTK_CONTAINER(window), notebook); - - g_signal_connect(notebook, "destroy", - G_CALLBACK(disconnect_prefs_cb), plugin); - - - /**********************/ - /* connect to signals */ - /**********************/ - - /****************/ - /* account page */ - /****************/ - e = GTK_WIDGET(gtk_builder_get_object (builder, "account_twitter")); - g_object_set_data(G_OBJECT(e), "pref", OPT_SCREEN_NAME_TWITTER); - text = purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER); - gtk_entry_set_text(GTK_ENTRY(e), text); - g_signal_connect(e, "changed", - G_CALLBACK(text_changed_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "account_wassr")); - g_object_set_data(G_OBJECT(e), "pref", OPT_SCREEN_NAME_WASSR); - text = purple_prefs_get_string(OPT_SCREEN_NAME_WASSR); - gtk_entry_set_text(GTK_ENTRY(e), text); - g_signal_connect(e, "changed", - G_CALLBACK(text_changed_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "account_identica")); - g_object_set_data(G_OBJECT(e), "pref", OPT_SCREEN_NAME_IDENTICA); - text = purple_prefs_get_string(OPT_SCREEN_NAME_IDENTICA); - gtk_entry_set_text(GTK_ENTRY(e), text); - g_signal_connect(e, "changed", - G_CALLBACK(text_changed_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "account_jisko")); - g_object_set_data(G_OBJECT(e), "pref", OPT_SCREEN_NAME_JISKO); - text = purple_prefs_get_string(OPT_SCREEN_NAME_JISKO); - gtk_entry_set_text(GTK_ENTRY(e), text); - g_signal_connect(e, "changed", - G_CALLBACK(text_changed_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "account_api")); - g_object_set_data(G_OBJECT(e), "pref", OPT_API_BASE_POST); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_API_BASE_POST)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - purple_prefs_connect_callback(plugin, OPT_API_BASE_POST, /* xxx divide? */ - api_base_post_cb, NULL); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "account_api_password")); - g_object_set_data(G_OBJECT(e), "pref", OPT_PASSWORD_TWITTER); - - gtk_entry_set_visibility(GTK_ENTRY(e), FALSE); - if (gtk_entry_get_invisible_char(GTK_ENTRY(e)) == '*') - gtk_entry_set_invisible_char(GTK_ENTRY(e), PIDGIN_INVISIBLE_CHAR); - - text = purple_prefs_get_string(OPT_PASSWORD_TWITTER); - gtk_entry_set_text(GTK_ENTRY(e), text); - g_signal_connect(e, "changed", - G_CALLBACK(text_changed_cb), &e); - - - /* interval spin */ - e = GTK_WIDGET(gtk_builder_get_object (builder, - "account_api_get_interval_spin")); - g_object_set_data(G_OBJECT(e), "pref", OPT_API_BASE_GET_INTERVAL); - - spin = GTK_SPIN_BUTTON(e); - - value = purple_prefs_get_int(OPT_API_BASE_GET_INTERVAL); - twitter_debug("spin value = %d\n", value); - - adjust = gtk_adjustment_new(value, 40, 3600, 10, 100, 100); - gtk_spin_button_set_adjustment(spin, GTK_ADJUSTMENT(adjust)); - gtk_widget_set_size_request(GTK_WIDGET(spin), 50, -1); - - if(value == 0) { - value = TWITTER_DEFAULT_INTERVAL; - purple_prefs_set_int(OPT_API_BASE_GET_INTERVAL, value); - } - gtk_spin_button_set_value(GTK_SPIN_BUTTON(e), (gdouble)value); - g_signal_connect(e, "value-changed", - G_CALLBACK(spin_changed_cb), &e); - purple_prefs_connect_callback(plugin, OPT_API_BASE_GET_INTERVAL, - interval_prefs_cb, NULL); - - - - /********************/ - /* translation page */ - /********************/ - e = GTK_WIDGET(gtk_builder_get_object (builder, "translation_recipient")); - g_object_set_data(G_OBJECT(e), "pref", OPT_TRANSLATE_RECIPIENT); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_TRANSLATE_RECIPIENT)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "translation_sender")); - g_object_set_data(G_OBJECT(e), "pref", OPT_TRANSLATE_SENDER); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_TRANSLATE_SENDER)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "translation_channel")); - g_object_set_data(G_OBJECT(e), "pref", OPT_TRANSLATE_CHANNEL); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_TRANSLATE_CHANNEL)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - - - /***************/ - /* filter page */ - /***************/ - e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_filter_check")); - g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_FILTER)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_exclude_reply_check")); - g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_EXCLUDE_REPLY); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_FILTER_EXCLUDE_REPLY)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_twitter")); - g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_TWITTER); - text = purple_prefs_get_string(OPT_FILTER_TWITTER); - gtk_entry_set_text(GTK_ENTRY(e), text); - g_signal_connect(e, "changed", - G_CALLBACK(text_changed_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_wassr")); - g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_WASSR); - text = purple_prefs_get_string(OPT_FILTER_WASSR); - gtk_entry_set_text(GTK_ENTRY(e), text); - g_signal_connect(e, "changed", - G_CALLBACK(text_changed_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_identica")); - g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_IDENTICA); - text = purple_prefs_get_string(OPT_FILTER_IDENTICA); - gtk_entry_set_text(GTK_ENTRY(e), text); - g_signal_connect(e, "changed", - G_CALLBACK(text_changed_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_jisko")); - g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_JISKO); - text = purple_prefs_get_string(OPT_FILTER_JISKO); - gtk_entry_set_text(GTK_ENTRY(e), text); - g_signal_connect(e, "changed", - G_CALLBACK(text_changed_cb), &e); - - - - /*************/ - /* icon page */ - /*************/ - e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_show_icon")); - g_object_set_data(G_OBJECT(e), "pref", OPT_SHOW_ICON); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_SHOW_ICON)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - /* icon size spin */ - e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_icon_size_spin")); - g_object_set_data(G_OBJECT(e), "pref", OPT_ICON_SIZE); - - spin = GTK_SPIN_BUTTON(e); - - value = purple_prefs_get_int(OPT_ICON_SIZE); - twitter_debug("spin value = %d\n", value); - - adjust = gtk_adjustment_new(value, 16, 128, 4, 4, 4); - gtk_spin_button_set_adjustment(spin, GTK_ADJUSTMENT(adjust)); - gtk_widget_set_size_request(GTK_WIDGET(spin), 50, -1); - - if(value == 0) { - value = DEFAULT_ICON_SIZE; - purple_prefs_set_int(OPT_ICON_SIZE, value); - } - gtk_spin_button_set_value(GTK_SPIN_BUTTON(e), (gdouble)value); - g_signal_connect(e, "value-changed", - G_CALLBACK(spin_changed_cb), &e); - purple_prefs_connect_callback(plugin, OPT_ICON_SIZE, - icon_size_prefs_cb, NULL); - - /* enable update */ - e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_enable_update")); - g_object_set_data(G_OBJECT(e), "pref", OPT_UPDATE_ICON); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_UPDATE_ICON)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - /* max count spin */ - e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_max_count_spin")); - g_object_set_data(G_OBJECT(e), "pref", OPT_ICON_MAX_COUNT); - - spin = GTK_SPIN_BUTTON(e); - - value = purple_prefs_get_int(OPT_ICON_MAX_COUNT); - twitter_debug("spin value = %d\n", value); - - adjust = gtk_adjustment_new(value, 2, 10000, 1, 10, 10); - gtk_spin_button_set_adjustment(spin, GTK_ADJUSTMENT(adjust)); - gtk_widget_set_size_request(GTK_WIDGET(spin), 50, -1); - - if(value == 0) { - value = DEFAULT_ICON_MAX_COUNT; - purple_prefs_set_int(OPT_ICON_MAX_COUNT, value); - } - gtk_spin_button_set_value(GTK_SPIN_BUTTON(e), (gdouble)value); - g_signal_connect(e, "value-changed", - G_CALLBACK(spin_changed_cb), &e); - - - /* max days spin */ - e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_max_days_spin")); - g_object_set_data(G_OBJECT(e), "pref", OPT_ICON_MAX_DAYS); - - spin = GTK_SPIN_BUTTON(e); - - value = purple_prefs_get_int(OPT_ICON_MAX_DAYS); - twitter_debug("spin value = %d\n", value); - - adjust = gtk_adjustment_new(value, 1, 180, 1, 10, 10); - gtk_spin_button_set_adjustment(spin, GTK_ADJUSTMENT(adjust)); - gtk_widget_set_size_request(GTK_WIDGET(spin), 50, -1); - - if(value == 0) { - value = DEFAULT_ICON_MAX_DAYS; - purple_prefs_set_int(OPT_ICON_MAX_DAYS, value); - } - gtk_spin_button_set_value(GTK_SPIN_BUTTON(e), (gdouble)value); - g_signal_connect(e, "value-changed", - G_CALLBACK(spin_changed_cb), &e); - - - - /**************/ - /* sound page */ - /**************/ - e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_recip_check")); - g_object_set_data(G_OBJECT(e), "pref", OPT_PLAYSOUND_RECIPIENT); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_PLAYSOUND_RECIPIENT)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_recip_list")); - g_object_set_data(G_OBJECT(e), "pref", OPT_USERLIST_RECIPIENT); - text = purple_prefs_get_string(OPT_USERLIST_RECIPIENT); - gtk_entry_set_text(GTK_ENTRY(e), text); - g_signal_connect(e, "changed", - G_CALLBACK(text_changed_cb), &e); - - /* recipient combobox */ - e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_recip_combo")); - gtk_combo_box_set_active(GTK_COMBO_BOX(e), - purple_prefs_get_int(OPT_SOUNDID_RECIPIENT)); - g_object_set_data(G_OBJECT(e), "pref", OPT_SOUNDID_RECIPIENT); - g_signal_connect(e, "changed", - G_CALLBACK(combo_changed_cb), &e); - - - - e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_send_check")); - g_object_set_data(G_OBJECT(e), "pref", OPT_PLAYSOUND_SENDER); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_PLAYSOUND_SENDER)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_send_list")); - g_object_set_data(G_OBJECT(e), "pref", OPT_USERLIST_SENDER); - text = purple_prefs_get_string(OPT_USERLIST_SENDER); - gtk_entry_set_text(GTK_ENTRY(e), text); - g_signal_connect(e, "changed", - G_CALLBACK(text_changed_cb), &e); - - /* sender combobox */ - e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_send_combo")); - gtk_combo_box_set_active(GTK_COMBO_BOX(e), - purple_prefs_get_int(OPT_SOUNDID_RECIPIENT)); - g_object_set_data(G_OBJECT(e), "pref", OPT_SOUNDID_SENDER); - g_signal_connect(e, "changed", - G_CALLBACK(combo_changed_cb), &e); - - - - - /****************/ - /* utility page */ - /****************/ - e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_counter")); - g_object_set_data(G_OBJECT(e), "pref", OPT_COUNTER); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_COUNTER)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - purple_prefs_connect_callback(plugin, OPT_COUNTER, - counter_prefs_cb, NULL); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_pseudo")); - g_object_set_data(G_OBJECT(e), "pref", OPT_ESCAPE_PSEUDO); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_ESCAPE_PSEUDO)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_oops")); - g_object_set_data(G_OBJECT(e), "pref", OPT_SUPPRESS_OOPS); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_SUPPRESS_OOPS)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_strip_excess_lf")); - g_object_set_data(G_OBJECT(e), "pref", OPT_STRIP_EXCESS_LF); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_STRIP_EXCESS_LF)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_notify")); - g_object_set_data(G_OBJECT(e), "pref", OPT_PREVENT_NOTIFICATION); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_PREVENT_NOTIFICATION)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_log_output")); - g_object_set_data(G_OBJECT(e), "pref", OPT_LOG_OUTPUT); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), - purple_prefs_get_bool(OPT_LOG_OUTPUT)); - g_signal_connect(e, "toggled", - G_CALLBACK(bool_toggled_cb), &e); - - - /* all done */ - gtk_widget_show_all(notebook); - return notebook; -} - -static PidginPluginUiInfo ui_info = { - prefs_get_frame, - 0, /* page number - reserved */ - NULL, /* reserved 1 */ - NULL, /* reserved 2 */ - NULL, /* reserved 3 */ - NULL /* reserved 4 */ -}; - -static PurplePluginInfo info = { - PURPLE_PLUGIN_MAGIC, - PURPLE_MAJOR_VERSION, - PURPLE_MINOR_VERSION, - PURPLE_PLUGIN_STANDARD, /**< type */ - PIDGIN_PLUGIN_TYPE, /**< ui_req */ - 0, /**< flags */ - NULL, /**< deps */ - PURPLE_PRIORITY_DEFAULT, /**< priority */ - PLUGIN_ID, /**< id */ - "Pidgin-Twitter", /**< name */ - "0.8.0", /**< version */ - "provides useful features for twitter", /** summary */ - "provides useful features for twitter", /** desc */ - "Yoshiki Yazawa, mikanbako, \nKonosuke Watanabe, IWATA Ray, \nmojin, umq, \nthe pidging-twitter team", /**< author */ - "http://www.honeyplanet.jp/pidgin-twitter/", /**< homepage */ - load_plugin, /**< load */ - unload_plugin, /**< unload */ - NULL, /**< destroy */ - &ui_info, /**< ui_info */ - NULL, /**< extra_info */ - NULL, /**< pref info */ - NULL -}; - -static void -init_plugin(PurplePlugin *plugin) -{ - char *dirname = NULL; - - g_type_init(); - dirname = g_build_filename(purple_user_dir(), "pidgin-twitter", "icons", NULL); - if(dirname) - purple_prefs_add_string(OPT_ICON_DIR, dirname); - g_free(dirname); - - /* add plugin preferences */ - purple_prefs_add_none(OPT_PIDGINTWITTER); - purple_prefs_add_bool(OPT_TRANSLATE_RECIPIENT, TRUE); - purple_prefs_add_bool(OPT_TRANSLATE_SENDER, TRUE); - purple_prefs_add_bool(OPT_TRANSLATE_CHANNEL, TRUE); - purple_prefs_add_bool(OPT_ESCAPE_PSEUDO, TRUE); - purple_prefs_add_bool(OPT_STRIP_EXCESS_LF, TRUE); - - purple_prefs_add_bool(OPT_PLAYSOUND_RECIPIENT, TRUE); - purple_prefs_add_bool(OPT_PLAYSOUND_SENDER, TRUE); - purple_prefs_add_int(OPT_SOUNDID_RECIPIENT, PURPLE_SOUND_POUNCE_DEFAULT); - purple_prefs_add_string(OPT_USERLIST_RECIPIENT, DEFAULT_LIST); - purple_prefs_add_int(OPT_SOUNDID_SENDER, PURPLE_SOUND_POUNCE_DEFAULT); - purple_prefs_add_string(OPT_USERLIST_SENDER, DEFAULT_LIST); - - purple_prefs_add_bool(OPT_COUNTER, TRUE); - purple_prefs_add_bool(OPT_SUPPRESS_OOPS, TRUE); - purple_prefs_add_bool(OPT_PREVENT_NOTIFICATION, FALSE); - - purple_prefs_add_bool(OPT_API_BASE_POST, FALSE); - purple_prefs_add_int(OPT_API_BASE_GET_INTERVAL, TWITTER_DEFAULT_INTERVAL); - purple_prefs_add_string(OPT_SCREEN_NAME_TWITTER, EMPTY); - purple_prefs_add_string(OPT_PASSWORD_TWITTER, EMPTY); - purple_prefs_add_string(OPT_SCREEN_NAME_WASSR, EMPTY); - purple_prefs_add_string(OPT_SCREEN_NAME_IDENTICA, EMPTY); - purple_prefs_add_string(OPT_SCREEN_NAME_JISKO, EMPTY); - - purple_prefs_add_bool(OPT_SHOW_ICON, TRUE); - purple_prefs_add_int(OPT_ICON_SIZE, DEFAULT_ICON_SIZE); - purple_prefs_add_bool(OPT_UPDATE_ICON, TRUE); - purple_prefs_add_int(OPT_ICON_MAX_COUNT, DEFAULT_ICON_MAX_COUNT); - purple_prefs_add_int(OPT_ICON_MAX_DAYS, DEFAULT_ICON_MAX_DAYS); - purple_prefs_add_bool(OPT_LOG_OUTPUT, FALSE); - - purple_prefs_add_bool(OPT_FILTER, TRUE); - purple_prefs_add_bool(OPT_FILTER_EXCLUDE_REPLY, TRUE); - purple_prefs_add_string(OPT_FILTER_TWITTER, DEFAULT_LIST); - purple_prefs_add_string(OPT_FILTER_WASSR, DEFAULT_LIST); - purple_prefs_add_string(OPT_FILTER_IDENTICA, DEFAULT_LIST); - purple_prefs_add_string(OPT_FILTER_JISKO, DEFAULT_LIST); -} - -PURPLE_INIT_PLUGIN(pidgin_twitter, init_plugin, info) diff -r a37ae6c8fa66 -r c2620a99622b pidgin-twitter.h --- a/pidgin-twitter.h Fri Nov 21 22:22:33 2008 +0900 +++ b/pidgin-twitter.h Sat Nov 22 18:01:18 2008 +0900 @@ -15,14 +15,20 @@ #include #include -#include "gtkplugin.h" +#include +#include +#include +#include +#include +#include +#include +#include + #include "util.h" -#include "debug.h" -#include "connection.h" -#include "version.h" -#include "sound.h" -#include "gtkconv.h" -#include "gtkimhtml.h" +#include "prefs.h" +#include "twitter_api.h" +#include "icon.h" +#include "main.h" /* regp id */ enum { @@ -84,6 +90,11 @@ guint id; } status_t; +typedef struct _source { + guint id; + PurpleConversation *conv; +} source_t; + /* container for api based post */ typedef struct twitter_message { PurpleAccount *account; @@ -197,80 +208,11 @@ #define DEFAULT_ICON_MAX_COUNT (50) #define DEFAULT_ICON_MAX_DAYS (7) #define DAYS_TO_SECONDS(d) ((d) * 86400) +#define NUM_REGPS (13) +#define NUM_SERVICES (4) /* twitter, wassr, identica, jisko. */ /* debug macros */ #define twitter_debug(fmt, ...) do { if(purple_prefs_get_bool(OPT_LOG_OUTPUT)) purple_debug(PURPLE_DEBUG_INFO, PLUGIN_NAME, "%s():%4d: " fmt, __FUNCTION__, (int)__LINE__, ## __VA_ARGS__); } while(0); #define twitter_error(fmt, ...) do { if(purple_prefs_get_bool(OPT_LOG_OUTPUT)) purple_debug(PURPLE_DEBUG_ERROR, PLUGIN_NAME, "%s():%4d: " fmt, __FUNCTION__, (int)__LINE__, ## __VA_ARGS__); } while(0); -/* prototypes */ -static void escape(gchar **str); -static gboolean sending_im_cb(PurpleAccount *account, char *recipient, char **buffer, void *data); -static gboolean eval(const GMatchInfo *match_info, GString *result, gpointer user_data); -static void translate(gchar **str, gint which, gint service); -static void playsound(gchar **str, gint which); -static gboolean writing_im_cb(PurpleAccount *account, char *sender, char **buffer, PurpleConversation *conv, int flags, void *data); -static void insert_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *position, gchar *new_text, gint new_text_length, gpointer user_data); -static void delete_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *start_pos, GtkTextIter *end_pos, gpointer user_data); -static void detach_from_window(void); -static void detach_from_conv(PurpleConversation *conv, gpointer null); -static void delete_requested_icon_marks(PidginConversation *gtkconv, GHashTable *table); -static void attach_to_window(void); -static void attach_to_conv(PurpleConversation *conv, gpointer null); -static gboolean is_twitter_account(PurpleAccount *account, const char *name); -static gboolean is_twitter_conv(PurpleConversation *conv); -static gboolean is_wassr_account(PurpleAccount *account, const char *name); -static gboolean is_wassr_conv(PurpleConversation *conv); -static gboolean is_identica_account(PurpleAccount *account, const char *name); -static gboolean is_identica_conv(PurpleConversation *conv); -static gboolean is_jisko_account(PurpleAccount *account, const char *name); -static gboolean is_jisko_conv(PurpleConversation *conv); -static void conv_created_cb(PurpleConversation *conv, gpointer null); -static void deleting_conv_cb(PurpleConversation *conv); -static gboolean receiving_im_cb(PurpleAccount *account, char **sender, char **buffer, PurpleConversation *conv, PurpleMessageFlags *flags, void *data); -static void insert_icon_at_mark(GtkTextMark *requested_mark, gpointer user_data); -static void insert_requested_icon(const gchar *user_name, gint service); -static void got_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message); -static void request_icon(const char *user_name, gint service, gboolean renew); -static void mark_icon_for_user(GtkTextMark *mark, const gchar *user_name, gint service); -static gboolean displaying_im_cb(PurpleAccount *account, const char *who, char **message, PurpleConversation *conv, PurpleMessageFlags flags, void *data); -static void displayed_im_cb(PurpleAccount *account, const char *who, char *message, PurpleConversation *conv, PurpleMessageFlags flags); -static gboolean load_plugin(PurplePlugin *plugin); -static gboolean unload_plugin(PurplePlugin *plugin); -static void counter_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data); - -static void init_plugin(PurplePlugin *plugin); -static void remove_marks_func(gpointer key, gpointer value, gpointer user_data); -static void cancel_fetch_func(gpointer key, gpointer value, gpointer user_data); -static gint get_service_type(PurpleConversation *conv); -static GdkPixbuf *make_scaled_pixbuf(const gchar *url_text, gsize len); - -static void parse_user(xmlNode *user, status_t *st); -static void parse_status(xmlNode *status, status_t *st); -static void get_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message); -static gboolean get_status_with_api(gpointer data); -static void read_timestamp(const char *str, struct tm *res); - -static void strip_markup(gchar **str, gboolean escape); -static gchar *strip_html_markup(const gchar *src); -static GtkWidget *prefs_get_frame(PurplePlugin *plugin); - -/* more prototypes */ -static gboolean is_posted_message(status_t *status, guint lastid); -static void free_status(status_t *st); -static gboolean ensure_path_exists(const char *dir); -static gchar *twitter_memrchr(const gchar *s, int c, size_t n); -static void got_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message); -static void cleanup_hash_entry_func(gpointer key, gpointer value, gpointer user_data); -static void invalidate_icon_data_func(gpointer key, gpointer value, gpointer user_data); -static void icon_size_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data); -static void interval_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data); -static void text_changed_cb(gpointer *data); -static void bool_toggled_cb(gpointer *data); -static void spin_changed_cb(gpointer *data); -static void combo_changed_cb(gpointer *data); -static void disconnect_prefs_cb(GtkObject *object, gpointer data); -static GtkWidget *prefs_get_frame(PurplePlugin *plugin); -static void apply_filter(gchar **sender, gchar **buffer, PurpleMessageFlags *flags, int service); -static gint get_service_type_by_account(PurpleAccount *account, const char *sender); - #endif diff -r a37ae6c8fa66 -r c2620a99622b prefs.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/prefs.c Sat Nov 22 18:01:18 2008 +0900 @@ -0,0 +1,502 @@ +#include "pidgin-twitter.h" + +extern GHashTable *icon_hash[]; +extern source_t source; + + +/* prototypes */ +static void icon_size_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data); +static void interval_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data); +static void text_changed_cb(gpointer *data); +static void bool_toggled_cb(gpointer *data); +static void spin_changed_cb(gpointer *data); +static void combo_changed_cb(gpointer *data); +static void disconnect_prefs_cb(GtkObject *object, gpointer data); +static void counter_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data); + + +static void +counter_prefs_cb(const char *name, PurplePrefType type, + gconstpointer val, gpointer data) +{ + gboolean enabled = purple_prefs_get_bool(OPT_COUNTER); + + if(enabled) + attach_to_window(); + else + detach_from_window(); +} + +static void +icon_size_prefs_cb(const char *name, PurplePrefType type, + gconstpointer val, gpointer data) +{ + int i; + + /* invalidate icon cache */ + for(i = twitter_service; i < NUM_SERVICES; i++) { + g_hash_table_foreach(icon_hash[i], + (GHFunc)invalidate_icon_data_func, NULL); + } +} + +static void +interval_prefs_cb(const char *name, PurplePrefType type, + gconstpointer val, gpointer data) +{ + /* remove idle func */ + g_source_remove_by_user_data((gpointer)(source.conv)); + + /* add idle func */ + if(purple_prefs_get_bool(OPT_API_BASE_POST)) { + source.id = g_timeout_add_seconds( + purple_prefs_get_int(OPT_API_BASE_GET_INTERVAL), + get_status_with_api, (gpointer)(source.conv)); + } +} + +static void +text_changed_cb(gpointer *data) +{ + const gchar *text; + gchar *pref = (gchar *)g_object_get_data(G_OBJECT(data), "pref"); + text = gtk_entry_get_text(GTK_ENTRY(data)); + purple_prefs_set_string(pref, text); +} + +static void +bool_toggled_cb(gpointer *data) +{ + gchar *pref = (gchar *)g_object_get_data(G_OBJECT(data), "pref"); + gboolean value = purple_prefs_get_bool(pref); + purple_prefs_set_bool(pref, !value); +} + +static void +spin_changed_cb(gpointer *data) +{ + gchar *pref = (gchar *)g_object_get_data(G_OBJECT(data), "pref"); + + twitter_debug("called\n"); + + purple_prefs_set_int(pref, + gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(data))); +} + +static void +combo_changed_cb(gpointer *data) +{ + gint position; + gchar *pref = (gchar *)g_object_get_data(G_OBJECT(data), "pref"); + position = gtk_combo_box_get_active(GTK_COMBO_BOX(data)); + purple_prefs_set_int(pref, position); +} + +static void +disconnect_prefs_cb(GtkObject *object, gpointer data) +{ + PurplePlugin *plugin = (PurplePlugin *)data; + + purple_prefs_disconnect_by_handle(plugin); +} + +static void +api_base_post_cb(const char *name, PurplePrefType type, gconstpointer value, + gpointer data) +{ + signed_on_cb(NULL); + get_status_with_api((gpointer)(source.conv)); +} + +GtkWidget * +prefs_get_frame(PurplePlugin *plugin) +{ + GtkBuilder *builder; + GError *err = NULL; + gchar *filename; + GtkWidget *window, *notebook, *e; + const gchar *text; + GtkSpinButton *spin; + GtkObject *adjust; + gint value; +#ifdef _WIN32 + extern char binary_prefs_ui_start[]; + extern char binary_prefs_ui_size[]; +#endif + + builder = gtk_builder_new(); + +#ifdef _WIN32 + gtk_builder_add_from_string(builder, binary_prefs_ui_start, + (int)binary_prefs_ui_size, NULL); +#else + filename = g_build_filename(DATADIR, + "pidgin-twitter", "prefs.ui", NULL); + gtk_builder_add_from_file(builder, filename, &err); +#endif + + if(err) { + twitter_debug("%s\n", err->message); + g_free(filename); + return NULL; + } + + g_free(filename); + + gtk_builder_connect_signals(builder, NULL); + + window = GTK_WIDGET(gtk_builder_get_object(builder, "prefswindow")); + notebook = GTK_WIDGET(gtk_builder_get_object(builder, "prefsnotebook")); + + gtk_container_remove(GTK_CONTAINER(window), notebook); + + g_signal_connect(notebook, "destroy", + G_CALLBACK(disconnect_prefs_cb), plugin); + + + /**********************/ + /* connect to signals */ + /**********************/ + + /****************/ + /* account page */ + /****************/ + e = GTK_WIDGET(gtk_builder_get_object (builder, "account_twitter")); + g_object_set_data(G_OBJECT(e), "pref", OPT_SCREEN_NAME_TWITTER); + text = purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER); + gtk_entry_set_text(GTK_ENTRY(e), text); + g_signal_connect(e, "changed", + G_CALLBACK(text_changed_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "account_wassr")); + g_object_set_data(G_OBJECT(e), "pref", OPT_SCREEN_NAME_WASSR); + text = purple_prefs_get_string(OPT_SCREEN_NAME_WASSR); + gtk_entry_set_text(GTK_ENTRY(e), text); + g_signal_connect(e, "changed", + G_CALLBACK(text_changed_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "account_identica")); + g_object_set_data(G_OBJECT(e), "pref", OPT_SCREEN_NAME_IDENTICA); + text = purple_prefs_get_string(OPT_SCREEN_NAME_IDENTICA); + gtk_entry_set_text(GTK_ENTRY(e), text); + g_signal_connect(e, "changed", + G_CALLBACK(text_changed_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "account_jisko")); + g_object_set_data(G_OBJECT(e), "pref", OPT_SCREEN_NAME_JISKO); + text = purple_prefs_get_string(OPT_SCREEN_NAME_JISKO); + gtk_entry_set_text(GTK_ENTRY(e), text); + g_signal_connect(e, "changed", + G_CALLBACK(text_changed_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "account_api")); + g_object_set_data(G_OBJECT(e), "pref", OPT_API_BASE_POST); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_API_BASE_POST)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + purple_prefs_connect_callback(plugin, OPT_API_BASE_POST, /* xxx divide? */ + api_base_post_cb, NULL); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "account_api_password")); + g_object_set_data(G_OBJECT(e), "pref", OPT_PASSWORD_TWITTER); + + gtk_entry_set_visibility(GTK_ENTRY(e), FALSE); + if (gtk_entry_get_invisible_char(GTK_ENTRY(e)) == '*') + gtk_entry_set_invisible_char(GTK_ENTRY(e), PIDGIN_INVISIBLE_CHAR); + + text = purple_prefs_get_string(OPT_PASSWORD_TWITTER); + gtk_entry_set_text(GTK_ENTRY(e), text); + g_signal_connect(e, "changed", + G_CALLBACK(text_changed_cb), &e); + + + /* interval spin */ + e = GTK_WIDGET(gtk_builder_get_object (builder, + "account_api_get_interval_spin")); + g_object_set_data(G_OBJECT(e), "pref", OPT_API_BASE_GET_INTERVAL); + + spin = GTK_SPIN_BUTTON(e); + + value = purple_prefs_get_int(OPT_API_BASE_GET_INTERVAL); + twitter_debug("spin value = %d\n", value); + + adjust = gtk_adjustment_new(value, 40, 3600, 10, 100, 100); + gtk_spin_button_set_adjustment(spin, GTK_ADJUSTMENT(adjust)); + gtk_widget_set_size_request(GTK_WIDGET(spin), 50, -1); + + if(value == 0) { + value = TWITTER_DEFAULT_INTERVAL; + purple_prefs_set_int(OPT_API_BASE_GET_INTERVAL, value); + } + gtk_spin_button_set_value(GTK_SPIN_BUTTON(e), (gdouble)value); + g_signal_connect(e, "value-changed", + G_CALLBACK(spin_changed_cb), &e); + purple_prefs_connect_callback(plugin, OPT_API_BASE_GET_INTERVAL, + interval_prefs_cb, NULL); + + + + /********************/ + /* translation page */ + /********************/ + e = GTK_WIDGET(gtk_builder_get_object (builder, "translation_recipient")); + g_object_set_data(G_OBJECT(e), "pref", OPT_TRANSLATE_RECIPIENT); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_TRANSLATE_RECIPIENT)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "translation_sender")); + g_object_set_data(G_OBJECT(e), "pref", OPT_TRANSLATE_SENDER); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_TRANSLATE_SENDER)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "translation_channel")); + g_object_set_data(G_OBJECT(e), "pref", OPT_TRANSLATE_CHANNEL); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_TRANSLATE_CHANNEL)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + + + /***************/ + /* filter page */ + /***************/ + e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_filter_check")); + g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_FILTER)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_exclude_reply_check")); + g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_EXCLUDE_REPLY); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_FILTER_EXCLUDE_REPLY)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_twitter")); + g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_TWITTER); + text = purple_prefs_get_string(OPT_FILTER_TWITTER); + gtk_entry_set_text(GTK_ENTRY(e), text); + g_signal_connect(e, "changed", + G_CALLBACK(text_changed_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_wassr")); + g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_WASSR); + text = purple_prefs_get_string(OPT_FILTER_WASSR); + gtk_entry_set_text(GTK_ENTRY(e), text); + g_signal_connect(e, "changed", + G_CALLBACK(text_changed_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_identica")); + g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_IDENTICA); + text = purple_prefs_get_string(OPT_FILTER_IDENTICA); + gtk_entry_set_text(GTK_ENTRY(e), text); + g_signal_connect(e, "changed", + G_CALLBACK(text_changed_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_jisko")); + g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_JISKO); + text = purple_prefs_get_string(OPT_FILTER_JISKO); + gtk_entry_set_text(GTK_ENTRY(e), text); + g_signal_connect(e, "changed", + G_CALLBACK(text_changed_cb), &e); + + + + /*************/ + /* icon page */ + /*************/ + e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_show_icon")); + g_object_set_data(G_OBJECT(e), "pref", OPT_SHOW_ICON); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_SHOW_ICON)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + /* icon size spin */ + e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_icon_size_spin")); + g_object_set_data(G_OBJECT(e), "pref", OPT_ICON_SIZE); + + spin = GTK_SPIN_BUTTON(e); + + value = purple_prefs_get_int(OPT_ICON_SIZE); + twitter_debug("spin value = %d\n", value); + + adjust = gtk_adjustment_new(value, 16, 128, 4, 4, 4); + gtk_spin_button_set_adjustment(spin, GTK_ADJUSTMENT(adjust)); + gtk_widget_set_size_request(GTK_WIDGET(spin), 50, -1); + + if(value == 0) { + value = DEFAULT_ICON_SIZE; + purple_prefs_set_int(OPT_ICON_SIZE, value); + } + gtk_spin_button_set_value(GTK_SPIN_BUTTON(e), (gdouble)value); + g_signal_connect(e, "value-changed", + G_CALLBACK(spin_changed_cb), &e); + purple_prefs_connect_callback(plugin, OPT_ICON_SIZE, + icon_size_prefs_cb, NULL); + + /* enable update */ + e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_enable_update")); + g_object_set_data(G_OBJECT(e), "pref", OPT_UPDATE_ICON); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_UPDATE_ICON)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + /* max count spin */ + e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_max_count_spin")); + g_object_set_data(G_OBJECT(e), "pref", OPT_ICON_MAX_COUNT); + + spin = GTK_SPIN_BUTTON(e); + + value = purple_prefs_get_int(OPT_ICON_MAX_COUNT); + twitter_debug("spin value = %d\n", value); + + adjust = gtk_adjustment_new(value, 2, 10000, 1, 10, 10); + gtk_spin_button_set_adjustment(spin, GTK_ADJUSTMENT(adjust)); + gtk_widget_set_size_request(GTK_WIDGET(spin), 50, -1); + + if(value == 0) { + value = DEFAULT_ICON_MAX_COUNT; + purple_prefs_set_int(OPT_ICON_MAX_COUNT, value); + } + gtk_spin_button_set_value(GTK_SPIN_BUTTON(e), (gdouble)value); + g_signal_connect(e, "value-changed", + G_CALLBACK(spin_changed_cb), &e); + + + /* max days spin */ + e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_max_days_spin")); + g_object_set_data(G_OBJECT(e), "pref", OPT_ICON_MAX_DAYS); + + spin = GTK_SPIN_BUTTON(e); + + value = purple_prefs_get_int(OPT_ICON_MAX_DAYS); + twitter_debug("spin value = %d\n", value); + + adjust = gtk_adjustment_new(value, 1, 180, 1, 10, 10); + gtk_spin_button_set_adjustment(spin, GTK_ADJUSTMENT(adjust)); + gtk_widget_set_size_request(GTK_WIDGET(spin), 50, -1); + + if(value == 0) { + value = DEFAULT_ICON_MAX_DAYS; + purple_prefs_set_int(OPT_ICON_MAX_DAYS, value); + } + gtk_spin_button_set_value(GTK_SPIN_BUTTON(e), (gdouble)value); + g_signal_connect(e, "value-changed", + G_CALLBACK(spin_changed_cb), &e); + + + + /**************/ + /* sound page */ + /**************/ + e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_recip_check")); + g_object_set_data(G_OBJECT(e), "pref", OPT_PLAYSOUND_RECIPIENT); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_PLAYSOUND_RECIPIENT)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_recip_list")); + g_object_set_data(G_OBJECT(e), "pref", OPT_USERLIST_RECIPIENT); + text = purple_prefs_get_string(OPT_USERLIST_RECIPIENT); + gtk_entry_set_text(GTK_ENTRY(e), text); + g_signal_connect(e, "changed", + G_CALLBACK(text_changed_cb), &e); + + /* recipient combobox */ + e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_recip_combo")); + gtk_combo_box_set_active(GTK_COMBO_BOX(e), + purple_prefs_get_int(OPT_SOUNDID_RECIPIENT)); + g_object_set_data(G_OBJECT(e), "pref", OPT_SOUNDID_RECIPIENT); + g_signal_connect(e, "changed", + G_CALLBACK(combo_changed_cb), &e); + + + + e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_send_check")); + g_object_set_data(G_OBJECT(e), "pref", OPT_PLAYSOUND_SENDER); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_PLAYSOUND_SENDER)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_send_list")); + g_object_set_data(G_OBJECT(e), "pref", OPT_USERLIST_SENDER); + text = purple_prefs_get_string(OPT_USERLIST_SENDER); + gtk_entry_set_text(GTK_ENTRY(e), text); + g_signal_connect(e, "changed", + G_CALLBACK(text_changed_cb), &e); + + /* sender combobox */ + e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_send_combo")); + gtk_combo_box_set_active(GTK_COMBO_BOX(e), + purple_prefs_get_int(OPT_SOUNDID_RECIPIENT)); + g_object_set_data(G_OBJECT(e), "pref", OPT_SOUNDID_SENDER); + g_signal_connect(e, "changed", + G_CALLBACK(combo_changed_cb), &e); + + + + + /****************/ + /* utility page */ + /****************/ + e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_counter")); + g_object_set_data(G_OBJECT(e), "pref", OPT_COUNTER); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_COUNTER)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + purple_prefs_connect_callback(plugin, OPT_COUNTER, + counter_prefs_cb, NULL); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_pseudo")); + g_object_set_data(G_OBJECT(e), "pref", OPT_ESCAPE_PSEUDO); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_ESCAPE_PSEUDO)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_oops")); + g_object_set_data(G_OBJECT(e), "pref", OPT_SUPPRESS_OOPS); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_SUPPRESS_OOPS)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_strip_excess_lf")); + g_object_set_data(G_OBJECT(e), "pref", OPT_STRIP_EXCESS_LF); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_STRIP_EXCESS_LF)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_notify")); + g_object_set_data(G_OBJECT(e), "pref", OPT_PREVENT_NOTIFICATION); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_PREVENT_NOTIFICATION)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_log_output")); + g_object_set_data(G_OBJECT(e), "pref", OPT_LOG_OUTPUT); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), + purple_prefs_get_bool(OPT_LOG_OUTPUT)); + g_signal_connect(e, "toggled", + G_CALLBACK(bool_toggled_cb), &e); + + + /* all done */ + gtk_widget_show_all(notebook); + return notebook; +} diff -r a37ae6c8fa66 -r c2620a99622b prefs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/prefs.h Sat Nov 22 18:01:18 2008 +0900 @@ -0,0 +1,6 @@ +#ifndef _PIDGIN_TWITTER_PREFS_H_ +#define _PIDGIN_TWITTER_PREFS_H_ + +GtkWidget *prefs_get_frame(PurplePlugin *plugin); + +#endif diff -r a37ae6c8fa66 -r c2620a99622b twitter_api.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/twitter_api.c Sat Nov 22 18:01:18 2008 +0900 @@ -0,0 +1,564 @@ +#include "pidgin-twitter.h" + +static GList *postedlist = NULL; +static GList *statuseslist = NULL; + +/* prototypes */ +static void parse_user(xmlNode *user, status_t *st); +static void read_timestamp(const char *str, struct tm *res); +static void parse_status(xmlNode *status, status_t *st); + +static void free_status(status_t *st); +static gboolean is_posted_message(status_t *status, guint lastid); +static void get_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message); +static void post_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message); + + +/**************************/ +/* API base get functions */ +/**************************/ +/* xml parser */ +static void +parse_user(xmlNode *user, status_t *st) +{ + xmlNode *nptr; + + for(nptr = user->children; nptr != NULL; nptr = nptr->next) { + if(nptr->type == XML_ELEMENT_NODE) { + if(!xmlStrcmp(nptr->name, (xmlChar *)"screen_name")) { + gchar *str = (gchar *)xmlNodeGetContent(nptr); + st->screen_name = g_strdup(str); + xmlFree(str); + } + else if(!xmlStrcmp(nptr->name, (xmlChar *)"profile_image_url")) { + gchar *str = (gchar *)xmlNodeGetContent(nptr); + st->profile_image_url = g_strdup(str); + xmlFree(str); + } + } + } +} + +static gchar *day_of_week_name[] = { + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + NULL +}; + +static gchar *month_name[] = { + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + NULL +}; + +static void +read_timestamp(const char *str, struct tm *res) +{ + char day_of_week[4]; + char month[4]; + char time_offset[6]; + int day, hour, minute, second, year; + int i; + + if(str == NULL || res == NULL) + return; + + sscanf(str, "%s %s %d %d:%d:%d %s %d", + day_of_week, month, &day, + &hour, &minute, &second, + time_offset, &year); + + for(i=0; i<7; i++) { + if(!strcmp(day_of_week_name[i], day_of_week)) { + res->tm_wday = i; + } + } + for(i=0; i<12; i++) { + if(!strcmp(month_name[i], month)) { + res->tm_mon = i; + } + } + + res->tm_mday = day; + res->tm_hour = hour; + res->tm_min = minute; + res->tm_sec = second; + res->tm_year = year - 1900; +#ifndef _WIN32 + int offset = atoi(time_offset); + res->tm_gmtoff = -1 * (60 * 60 * offset / 100); +#endif + +} + +static void +parse_status(xmlNode *status, status_t *st) +{ + xmlNode *nptr; + + for(nptr = status->children; nptr != NULL; nptr = nptr->next) { + if(nptr->type == XML_ELEMENT_NODE) { + if(!xmlStrcmp(nptr->name, (xmlChar *)"created_at")) { + gchar *str = (gchar *)xmlNodeGetContent(nptr); + st->created_at = g_strdup(str); + + /* read time stamp */ + struct tm res; + memset(&res, 0x00, sizeof(struct tm)); + read_timestamp(str, &res); + tzset(); +#ifdef _WIN32 + st->time = mktime(&res) - timezone; +#else + st->time = mktime(&res) + res.tm_gmtoff; +#endif + + xmlFree(str); + } + else if(!xmlStrcmp(nptr->name, (xmlChar *)"id")) { + gchar *str = (gchar *)xmlNodeGetContent(nptr); + st->id = atoi(str); + xmlFree(str); + } + else if(!xmlStrcmp(nptr->name, (xmlChar *)"text")) { + gchar *str = (gchar *)xmlNodeGetContent(nptr); + st->text = g_strdup(str); + xmlFree(str); + } + else if(!xmlStrcmp(nptr->name, (xmlChar *)"user")) { + parse_user(nptr, st); + } + } + } +} + +static void +free_status(status_t *st) +{ + g_free(st->created_at); + g_free(st->text); + g_free(st->screen_name); + g_free(st->profile_image_url); +} + +static gboolean +is_posted_message(status_t *status, guint lastid) +{ + GList *pp = g_list_first(postedlist); + gboolean rv = FALSE; + + while(pp) { + GList *next; + status_t *posted = (status_t *)pp->data; + + next = g_list_next(pp); + + if(posted->id == status->id) { + rv = TRUE; + } + + if(posted->id <= lastid) { + free_status(posted); + g_free(pp->data); + postedlist = g_list_delete_link(postedlist, pp); + } + + pp = next; + } + + return rv; +} + +static void +get_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, + const gchar *url_text, size_t len, + const gchar *error_message) + +{ + xmlDocPtr doc; + xmlNode *nptr, *nptr2; + static guint lastid = 0; + PurpleConversation *conv; + GList *stp; + const gchar *start; + + g_return_if_fail(url_text != NULL); + + conv = (PurpleConversation *)user_data; + if(!conv) + return; + + /* skip to the beginning of xml */ + start = strstr(url_text, "children; nptr != NULL; nptr = nptr->next) { + if(nptr->type == XML_ELEMENT_NODE && + !xmlStrcmp(nptr->name, (xmlChar *)"statuses")) { + + for(nptr2 = nptr->children; nptr2 != NULL; nptr2 = nptr2->next) { + if(nptr2->type == XML_ELEMENT_NODE && + !xmlStrcmp(nptr2->name, (xmlChar *)"status")) { + status_t *st = g_new0(status_t, 1); + statuseslist = g_list_prepend(statuseslist, st); + parse_status(nptr2, st); + } + } + } + } + + xmlFreeDoc(doc); + xmlCleanupParser(); + + /* process statuseslist */ + stp = g_list_first(statuseslist); + while(stp) { + GList *next; + status_t *st = (status_t *)stp->data; + + next = g_list_next(stp); + + if(st->id > lastid && !is_posted_message(st, lastid)) { + gchar *msg = NULL; + gchar *sender = NULL; + + sender = g_strdup("twitter@twitter.com"); + + PurpleMessageFlags flag = PURPLE_MESSAGE_RECV; + + msg = g_strdup_printf("%s: %s", st->screen_name, st->text); + + /* apply filter*/ + if(purple_prefs_get_bool(OPT_FILTER)) { + apply_filter(&sender, &msg, &flag, twitter_service); + } + if(sender && msg) { + purple_conv_im_write(conv->u.im, + sender, + msg, + flag, + st->time); + } + lastid = st->id; + + g_free(sender); + g_free(msg); + } + + free_status(st); + g_free(stp->data); + statuseslist = g_list_delete_link(statuseslist, stp); + + stp = next; + } +} + +/* status fetching function. it will be called periodically. */ +gboolean +get_status_with_api(gpointer data) +{ + /* fetch friends time line */ + char *request, *header; + char *basic_auth, *basic_auth_encoded; + + twitter_debug("called\n"); + + /* if disabled, just return */ + if(!purple_prefs_get_bool(OPT_API_BASE_POST)) + return TRUE; + + const char *screen_name = + purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER); + const char *password = + purple_prefs_get_string(OPT_PASSWORD_TWITTER); + + if (!screen_name || !password || !screen_name[0] || !password[0]) { + twitter_debug("screen_name or password is empty\n"); + return TRUE; + } + + /* auth */ + basic_auth = g_strdup_printf("%s:%s", screen_name, password); + basic_auth_encoded = purple_base64_encode((unsigned char *)basic_auth, + strlen(basic_auth)); + g_free(basic_auth); + + /* header */ + header = g_strdup_printf(TWITTER_STATUS_GET, basic_auth_encoded); + request = g_strconcat(header, "\r\n", NULL); + + /* invoke fetch */ + purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE, + NULL, TRUE, request, TRUE, + get_status_with_api_cb, data); + + g_free(header); + g_free(basic_auth_encoded); + g_free(request); + + return TRUE; +} + +/****************************/ +/* API based post functions */ +/****************************/ +static void +post_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, + const gchar *url_text, size_t len, + const gchar *error_message) +{ + twitter_message_t *tm = (twitter_message_t *)user_data; + gchar *msg = NULL; + char *p1 = NULL, *p2 = NULL; + int error = 1; + PurpleConversation *conv; + + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, + "twitter@twitter.com", + tm->account); + if (!conv) { + twitter_debug("failed to get conversation\n"); + goto fin; + } + + if (error_message) { + /* connection failed or something */ + msg = g_strdup_printf("Local error: %s", error_message); + } else { + int code = -1; + + if ((strncmp(url_text, "HTTP/1.0", strlen("HTTP/1.0")) == 0 + || strncmp(url_text, "HTTP/1.1", strlen("HTTP/1.1")) == 0)) { + + p1 = strchr(url_text, ' '); + + if (p1) { + p1++; + p2 = strchr(p1, ' '); + if (p2) + p2++; + else + p2 = NULL; + } + } + + code = atoi(p1); + + if (code == 200) { + error = 0; + } else { + switch (code) { + case 400: + msg = g_strdup("Invalid request. Too many updates?"); + break; + case 401: + msg = g_strdup("Authorization failed."); + break; + case 403: + msg = g_strdup("Your update has been refused by Twitter server " + "for some reason."); + break; + case 404: + msg = g_strdup("Requested URI is not found."); + break; + case 500: + msg = g_strdup("Server error."); + break; + case 502: + msg = g_strdup("Twitter is down or under maintenance."); + break; + case 503: + msg = g_strdup("Twitter is extremely crowded. " + "Try again later."); + break; + default: + msg = g_strdup_printf("Unknown error. (%d %s)", + code, p2 ? p2 : ""); + break; + } + } + } + + if (!error) { + purple_conv_im_write(conv->u.im, + purple_account_get_username(tm->account), + tm->status, PURPLE_MESSAGE_SEND, tm->time); + + /* cache message ID that posted via API */ + gchar *start = NULL; + xmlDocPtr doc; + xmlNode *nptr; + + start = strstr(url_text, "children; nptr != NULL; nptr = nptr->next) { + if(nptr->type == XML_ELEMENT_NODE && + !xmlStrcmp(nptr->name, (xmlChar *)"status")) { + status_t *st = g_new0(status_t, 1); + postedlist = g_list_prepend(postedlist, st); + parse_status(nptr, st); + } + } + + xmlFreeDoc(doc); + xmlCleanupParser(); + + } else { + gchar *m; + m = g_strdup_printf("%s
%s", + msg, tm->status); + /* FIXME: too strong. it should be more smart */ + purple_conv_im_write(conv->u.im, + purple_account_get_username(tm->account), + m, PURPLE_MESSAGE_ERROR, time(NULL)); + g_free(m); + } + + fin: + if (msg) + g_free(msg); + + if (tm) { + if (tm->status) + g_free(tm->status); + g_free(tm); + } + +} + +void +post_status_with_api(PurpleAccount *account, char **buffer) +{ + char *request, *status, *header; + const char *url_encoded = purple_url_encode(*buffer); + char *basic_auth, *basic_auth_encoded; + + twitter_message_t *tm; + + const char *screen_name = + purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER); + const char *password = purple_prefs_get_string(OPT_PASSWORD_TWITTER); + + twitter_debug("tm.account: %s\n", + purple_account_get_username(account)); + + if (!screen_name || !password || !screen_name[0] || !password[0]) { + twitter_debug("screen_name or password is empty\n"); + return; + } + + tm = g_new(twitter_message_t, 1); + tm->account = account; + tm->status = g_strdup(*buffer); + tm->time = time(NULL); + + basic_auth = g_strdup_printf("%s:%s", screen_name, password); + basic_auth_encoded = purple_base64_encode((unsigned char *)basic_auth, + strlen(basic_auth)); + g_free(basic_auth); + + status = g_strdup_printf(TWITTER_STATUS_FORMAT, url_encoded); + header = g_strdup_printf(TWITTER_STATUS_POST, basic_auth_encoded, + (int)strlen(status)); + + request = g_strconcat(header, "\r\n", status, "\r\n", NULL); + + purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE, + NULL, TRUE, request, TRUE, + post_status_with_api_cb, tm); + + g_free(header); + g_free(basic_auth_encoded); + g_free(status); + g_free(request); + +} + +void +signed_on_cb(PurpleConnection *gc) +{ + PurpleBuddyList *list = purple_get_blist(); + PurpleBlistNode *gnode, *cnode, *bnode; + PurpleBuddy *b; + + twitter_debug("called\n"); + + if(!purple_prefs_get_bool(OPT_API_BASE_POST)) + return; + + if (!list) + return; + + twitter_debug("scan list\n"); + + for (gnode = list->root; gnode; gnode = gnode->next) { + if(!PURPLE_BLIST_NODE_IS_GROUP(gnode)) + continue; + + for(cnode = gnode->child; cnode; cnode = cnode->next) { + + if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) + continue; + + for(bnode = cnode->child; bnode; bnode = bnode->next) { + if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) + continue; + + b = (PurpleBuddy *)bnode; + + if(!PURPLE_BUDDY_IS_ONLINE(b)) { + const char *name; + PurpleAccount *account; + name = purple_buddy_get_name(b); + account = purple_buddy_get_account(b); + if (is_twitter_account(account, name)) { + PurpleConversation *gconv; + gconv = purple_find_conversation_with_account( + PURPLE_CONV_TYPE_IM, name, account); + if (!gconv) { + gconv = purple_conversation_new( + PURPLE_CONV_TYPE_IM, account, name); + } + } + } + } + } + } +} diff -r a37ae6c8fa66 -r c2620a99622b twitter_api.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/twitter_api.h Sat Nov 22 18:01:18 2008 +0900 @@ -0,0 +1,8 @@ +#ifndef _PIDGIN_TWITTER_TWITTER_API_H_ +#define _PIDGIN_TWITTER_TWITTER_API_H_ + +void post_status_with_api(PurpleAccount *account, char **buffer); +gboolean get_status_with_api(gpointer data); +void signed_on_cb(PurpleConnection *gc); + +#endif diff -r a37ae6c8fa66 -r c2620a99622b util.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/util.c Sat Nov 22 18:01:18 2008 +0900 @@ -0,0 +1,259 @@ +#include "pidgin-twitter.h" + +extern GRegex *regp[]; + +/* prototypes */ +static gchar *twitter_memrchr(const gchar *s, int c, size_t n); + + + +/* this function has been taken from autoaccept plugin */ +gboolean +ensure_path_exists(const char *dir) +{ + if (!g_file_test(dir, G_FILE_TEST_IS_DIR)) { + if (purple_build_dir(dir, S_IRUSR | S_IWUSR | S_IXUSR)) + return FALSE; + } + + return TRUE; +} + +static gchar * +twitter_memrchr(const gchar *s, int c, size_t n) +{ + int nn = n; + + g_return_val_if_fail(s != NULL, NULL); + + while(nn+1) { + if((int)*(s+nn) == c) + return (gchar *)(s+nn); + nn--; + } + return NULL; +} + +static gchar *html_tags[] = { + "
", + "", + "", + "

", + "

", + "
", + "", + "", + "", + "", + "", + "", + "
", + "
", + "", + "", + NULL +}; + +gchar * +strip_html_markup(const gchar *src) +{ + gchar *head, *tail; /* head and tail of html */ + gchar *begin, *end; /* begin:< end:> */ + gchar *html, *str; /* copied src and string to be returned */ +/* gchar *vis1, *vis2; */ /* begin and end of address part */ + gchar *startp; /* starting point marker */ + gchar **tagp; /* tag iterator */ + gchar *tmp, *tmp2; /* scratches */ + + g_return_val_if_fail(src != NULL, NULL); + + const gchar *ptr, *ent; + gchar *ptr2; + gint entlen; + + /* unescape &x; */ + html = g_malloc0(strlen(src) + 1); + ptr2 = html; + for(ptr = src; *ptr; ) { + if(*ptr == '&') { + ent = purple_markup_unescape_entity(ptr, &entlen); + if(ent != NULL) { + while(*ent) { + *ptr2++ = *ent++; + } + ptr += entlen; + } + else { + *ptr2++ = *ptr++; + } + } + else { + *ptr2++ = *ptr++; + } + } /* for */ + + str = g_strdup("\0"); + + head = html; + tail = head + strlen(html); + startp = head; + +loop: + begin = NULL; + end = NULL; + + if(startp >= tail) { + g_free(html); + return str; + } + + end = strchr(startp, '>'); + if(end) { + begin = twitter_memrchr(startp, '<', end - startp); + if(begin < startp) + begin = NULL; + + if(!begin) { /* '>' found but no corresponding '<' */ + tmp = g_strndup(startp, end - startp + 1); /* concat until '>' */ + tmp2 = g_strconcat(str, tmp, NULL); + g_free(str); + g_free(tmp); + str = tmp2; + startp = end + 1; + goto loop; + } + } + else { /* neither '>' nor '<' were found */ + tmp = g_strconcat(str, startp, NULL); /* concat the rest */ + g_free(str); + str = tmp; + g_free(html); + return str; + } + + /* here, both < and > are found */ + /* concatenate leading part to dest */ + tmp = g_strndup(startp, begin - startp); + tmp2 = g_strconcat(str, tmp, NULL); + g_free(tmp); + g_free(str); + str = tmp2; + + /* find tag */ + for(tagp = html_tags; *tagp; tagp++) { + if(!g_ascii_strncasecmp(begin, *tagp, strlen(*tagp))) { + /* we found a valid tag */ + /* if tag is
. */ + startp = end + 1; + goto loop; + } +#else + /* anything else: discard whole <>. */ + startp = end + 1; + goto loop; +#endif + } /* valid tag */ + } + + /* no valid tag was found: copy */ + tmp = g_strndup(begin, end - begin + 1); + tmp2 = g_strconcat(str, tmp, NULL); + g_free(tmp); + g_free(str); + str = tmp2; + startp = end + 1; + goto loop; +} + +/* string utilities */ +void +escape(gchar **str) +{ + GMatchInfo *match_info = NULL; + gchar *newstr = NULL, *match = NULL; + gboolean flag = FALSE; + + /* search genuine command */ + g_regex_match(regp[COMMAND], *str, 0, &match_info); + while(g_match_info_matches(match_info)) { + match = g_match_info_fetch(match_info, 1); + twitter_debug("command = %s\n", match); + g_free(match); + g_match_info_next(match_info, NULL); + flag = TRUE; + } + g_match_info_free(match_info); + match_info = NULL; + + if(flag) + return; + + /* if not found, check pseudo command */ + g_regex_match(regp[PSEUDO], *str, 0, &match_info); + while(g_match_info_matches(match_info)) { + match = g_match_info_fetch(match_info, 1); + twitter_debug("pseudo = %s\n", match); + g_free(match); + g_match_info_next(match_info, NULL); + flag = TRUE; + } + g_match_info_free(match_info); + match_info = NULL; + + /* if there is pseudo one, escape it */ + if(flag) { + /* put ". " to the beginning of buffer */ + newstr = g_strdup_printf(". %s", *str); + twitter_debug("*str = %s newstr = %s\n", *str, newstr); + g_free(*str); + *str = newstr; + } +} + +void +strip_markup(gchar **str, gboolean escape) +{ + gchar *plain; + + plain = strip_html_markup(*str); + g_free(*str); + if(escape) { + *str = g_markup_escape_text(plain, -1); + g_free(plain); + } + else { + *str = plain; + } + twitter_debug("result=%s\n", *str); +} diff -r a37ae6c8fa66 -r c2620a99622b util.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/util.h Sat Nov 22 18:01:18 2008 +0900 @@ -0,0 +1,9 @@ +#ifndef _PIDGIN_TWITTER_UTIL_H_ +#define _PIDGIN_TWITTER_UTIL_H_ + +void escape(gchar **str); +void strip_markup(gchar **str, gboolean escape); +gchar *strip_html_markup(const gchar *src); +gboolean ensure_path_exists(const char *dir); + +#endif