changeset 330:cc41ee1f5d3a

implemented reply, favorite, retweet functionalities. these are quite raw, be careful!
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Mon, 12 Oct 2009 21:51:13 +0900
parents 300241bd1879
children b4c846870b3c
files main.c pidgin-twitter.h twitter_api.c util.c util.h
diffstat 5 files changed, 284 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/main.c	Mon Oct 12 01:45:36 2009 +0900
+++ b/main.c	Mon Oct 12 21:51:13 2009 +0900
@@ -31,6 +31,8 @@
 static GList *wassr_parrot_list = NULL;
 static GList *identica_parrot_list = NULL;
 static GList *ffeed_parrot_list = NULL;
+guint64 reply_to_msgid = 0;
+PurpleAccount *account_for_twitter = NULL;
 #ifdef _WIN32
 gboolean blink_state = FALSE;
 gboolean blink_modified = FALSE;
@@ -63,6 +65,7 @@
 #ifndef _WIN32
 extern gchar *sanitize_utf(const gchar *msg, gsize len, gsize *newlen) __attribute__ ((weak));
 #endif
+gboolean pt_uri_handler(const char *proto, const char *cmd, GHashTable *params);
 
 
 /*************/
@@ -392,7 +395,7 @@
             g_free(*buffer);
             *buffer = m;
         }
-    }
+    } /* send */
 
     /* strip all markups */
     strip_markup(buffer, TRUE);
@@ -405,6 +408,17 @@
         playsound(buffer, RECIPIENT);
     }
 
+    if(service == twitter_service) {
+        /* escape pseudo command (to show the same result as
+           sending message) */
+        if(purple_prefs_get_bool(OPT_ESCAPE_PSEUDO)) {
+            escape(buffer);
+        }
+
+        /* replace ptmsgid=123 with appropriate links */
+        twitter_add_links(buffer);
+    }
+
     /* translate */
     if(purple_prefs_get_bool(OPT_TRANSLATE_SENDER)) {
         if(service == ffeed_service)
@@ -432,11 +446,18 @@
         translate(buffer, GROUP_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 0
+    if(service == twitter_service) {
+        /* escape pseudo command (to show the same result as
+           sending message) */
+        if(purple_prefs_get_bool(OPT_ESCAPE_PSEUDO)) {
+            escape(buffer);
+        }
+
+        /* replace ptmsgid=123 with appropriate links */
+        twitter_add_links(buffer);
     }
+#endif
 
     if(purple_prefs_get_bool(OPT_STRIP_EXCESS_LF)) {
         translate(buffer, EXCESS_LF, service);
@@ -528,6 +549,9 @@
         break;
     }
 
+    if(count == 0)
+        reply_to_msgid = 0;
+
     box = gtkconv->toolbar;
     counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter");
     if(counter)
@@ -760,6 +784,7 @@
             get_status_with_api, (gpointer)conv);
         source.conv = conv;
         attach_to_conv(conv, NULL);
+        account_for_twitter = conv->account; /* xxx */
         break;
     case wassr_service:
     case identica_service:
@@ -1160,6 +1185,22 @@
 }
 
 static gboolean
+pt_url_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
+{
+	const gchar * url = gtk_imhtml_link_get_url(link);
+
+	purple_got_protocol_handler_uri(url);
+
+	return TRUE;
+}
+
+static gboolean
+pt_url_context_menu_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
+{
+	return TRUE;
+}
+
+static gboolean
 load_plugin(PurplePlugin *plugin)
 {
     int i;
@@ -1191,6 +1232,14 @@
                           "signed-on",
                           plugin, PURPLE_CALLBACK(signed_on_cb), NULL);
 
+	gtk_imhtml_class_register_protocol("PT://",
+                                       pt_url_clicked_cb,
+                                       pt_url_context_menu_cb);
+	purple_signal_connect(purple_get_core(),
+                          "uri-handler",
+                          plugin,
+                          PURPLE_CALLBACK(pt_uri_handler), NULL);
+
 
     /* compile regex */
     regp[RECIPIENT] = g_regex_new(P_RECIPIENT, 0, 0, NULL);
@@ -1211,6 +1260,7 @@
     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);
     regp[TRAIL_HASH] = g_regex_new(P_TRAIL_HASH, 0, 0, NULL);
+    regp[MESSAGE_ID] = g_regex_new(P_MESSAGE_ID, 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,
@@ -1289,6 +1339,13 @@
                              "signed-on",
                              plugin, PURPLE_CALLBACK(signed_on_cb));
 
+	gtk_imhtml_class_register_protocol("PT://", NULL, NULL);
+
+    /* should do? --yaz */
+	purple_signal_disconnect(purple_get_core(),
+                             "uri-handler",
+                             plugin, PURPLE_CALLBACK(pt_uri_handler));
+
     /* unreference regp */
     for(i = 0; i < NUM_REGPS; i++) {
         g_regex_unref(regp[i]);
--- a/pidgin-twitter.h	Mon Oct 12 01:45:36 2009 +0900
+++ b/pidgin-twitter.h	Mon Oct 12 21:51:13 2009 +0900
@@ -23,6 +23,7 @@
 #include <sound.h>
 #include <gtkconv.h>
 #include <gtkimhtml.h>
+#include <core.h>
 
 #include "util.h"
 #include "prefs.h"
@@ -49,7 +50,8 @@
     IMAGE_FFEED,
     SIZE_128_WASSR,
     EXCESS_LF,
-    TRAIL_HASH
+    TRAIL_HASH,
+    MESSAGE_ID
 };
 
 /* service id */
@@ -170,6 +172,8 @@
 #define TAG_FORMAT_TWITTER      "%s<a href='http://twitter.com/search?q=%%23%s'>#%s</a>"
 #define TAG_FORMAT_IDENTICA     "#<a href='http://identi.ca/tag/%s'>%s</a>"
 #define GROUP_FORMAT_IDENTICA   "!<a href='http://identi.ca/group/%s'>%s</a>"
+#define LINK_FORMAT_TWITTER     " <a href='PT://reply-twitter/?id=%s&user=%s'>R</a> <a href='PT://fav-twitter/?id=%s'>F</a> <a href='PT://retweet-twitter/?id=%s&user=%s&msg=%s'>RT</a>"
+#define TWITTER_REPLY_FORMAT    "in_reply_to_status_id=%llu"
 
 #define DEFAULT_LIST            "(list of users: separated with ' ,:;')"
 #define OOPS_MESSAGE            "<body>Oops! Your update was over 140 characters. We sent the short version to your friends (they can view the entire update on the web).<BR></body>"
@@ -194,6 +198,7 @@
 #define P_SIZE_128_WASSR    "\\.128\\."
 #define P_EXCESS_LF         "([\\r|\\n]{2,})"
 #define P_TRAIL_HASH        "( #\\s+$)"
+#define P_MESSAGE_ID        " ptmsgid=([0-9]+)$"
 
 /* twitter API specific macros */
 #define TWITTER_BASE_URL "http://twitter.com"
@@ -207,6 +212,13 @@
     "Authorization: Basic %s\r\n"                                    \
     "Content-Length: %d\r\n"
 #define TWITTER_STATUS_FORMAT "status=%s&source=pidgintwitter"
+
+#define TWITTER_FAV_POST "POST /favorites/create/%llu.xml HTTP/1.1\r\n" \
+    "Host: twitter.com\r\n"                                          \
+    "User-Agent: pidgin-twitter\r\n"                                 \
+    "Authorization: Basic %s\r\n"
+
+
 #define TWITTER_DEFAULT_INTERVAL (60)
 #define TWITTER_OLD_DEFAULT_ICON_URL "http://static.twitter.com/images/default_profile_bigger.png"
 #define TWITTER_DEFAULT_ICON_URL "http://s.twimg.com/images/default_profile_3_bigger.png"
@@ -234,7 +246,7 @@
 #define DEFAULT_ICON_MAX_COUNT (50)
 #define DEFAULT_ICON_MAX_DAYS (7)
 #define DAYS_TO_SECONDS(d) ((d) * 86400)
-#define NUM_REGPS (18)
+#define NUM_REGPS (19)
 #define NUM_SERVICES (5)          /* twitter, wassr, identica, jisko, ffeed. */
 
 /* debug macros */
--- a/twitter_api.c	Mon Oct 12 01:45:36 2009 +0900
+++ b/twitter_api.c	Mon Oct 12 21:51:13 2009 +0900
@@ -18,6 +18,9 @@
 extern gboolean blink_modified;
 #endif
 
+extern guint64 reply_to_msgid;
+extern PurpleAccount *account_for_twitter;
+
 /**************************/
 /* API base get functions */
 /**************************/
@@ -261,7 +264,9 @@
 
              PurpleMessageFlags flag = PURPLE_MESSAGE_RECV;
 
-             msg = g_strdup_printf("%s: %s", st->screen_name, st->text);
+             msg = g_strdup_printf("%s: %s ptmsgid=%llu",
+                                   st->screen_name, st->text,
+                                   (long long unsigned int)st->id);
 
              /* apply filter */
              if(purple_prefs_get_bool(OPT_FILTER)) {
@@ -323,7 +328,6 @@
     g_free(basic_auth);
 
     /* header */
-
     header = g_strdup_printf(TWITTER_STATUS_GET, count, basic_auth_encoded);
     request = g_strconcat(header, "\r\n", NULL);
 
@@ -509,7 +513,18 @@
     header = g_strdup_printf(TWITTER_STATUS_POST, basic_auth_encoded,
                              (int)strlen(status));
 
-    request = g_strconcat(header, "\r\n", status, "\r\n", NULL);
+    if(reply_to_msgid > 0) {
+        char *inreply = NULL;
+        inreply = g_strdup_printf(TWITTER_REPLY_FORMAT,
+                                  (long long unsigned int)reply_to_msgid);
+        request = g_strconcat(header, "\r\n", status, "\r\n",
+                              inreply, "\r\n", NULL);
+        reply_to_msgid = 0;
+        g_free(inreply);
+    }
+    else {
+        request = g_strconcat(header, "\r\n", status, "\r\n", NULL);
+    }
 
     purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE,
                                   NULL, TRUE, request, TRUE,
@@ -522,6 +537,50 @@
 
 }
 
+static void
+fav_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
+                        const gchar *url_text, size_t len,
+                        const gchar *error_message)
+{
+    /* dummy */
+}
+
+void
+fav_with_api(guint64 id)
+{
+    char *header, *request;
+    char *basic_auth, *basic_auth_encoded;
+
+    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;
+    }
+
+    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 = g_strdup_printf(TWITTER_FAV_POST,
+                             (long long unsigned int)id,
+                             basic_auth_encoded);
+    request = g_strconcat(header, "\r\n", NULL);
+
+    purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE,
+                                  NULL, TRUE, request, TRUE,
+                                  fav_with_api_cb, NULL);
+    twitter_debug("request = %s\n", request);
+
+    g_free(header);
+    g_free(basic_auth_encoded);
+    g_free(request);
+}
+
 void
 signed_on_cb(PurpleConnection *gc)
 {
@@ -547,6 +606,8 @@
         return;
     }
 
+    account_for_twitter = account;
+
     gconv = purple_find_conversation_with_account(
         PURPLE_CONV_TYPE_IM, name, account);
     if(!gconv) {
--- a/util.c	Mon Oct 12 01:45:36 2009 +0900
+++ b/util.c	Mon Oct 12 21:51:13 2009 +0900
@@ -1,9 +1,12 @@
 #include "pidgin-twitter.h"
 
 extern GRegex *regp[];
+extern guint64 reply_to_msgid;
+extern PurpleAccount *account_for_twitter;
 
 /* prototypes */
 static gchar *twitter_memrchr(const gchar *s, int c, size_t n);
+void fav_with_api(guint64 id);
 
 
 /* functions */
@@ -432,3 +435,143 @@
 
     return service;
 }
+
+gboolean
+pt_uri_handler(const char *proto, const char *cmd, GHashTable *params)
+{
+    char *idstr = NULL;
+	const char *acct_id = NULL;
+	PurpleConversation *conv = NULL;
+	PidginConversation *gtkconv = NULL;
+    guint64 msgid = 0;
+    gchar *sender = NULL, *recipient = NULL, *msg = NULL;
+
+    if(g_ascii_strcasecmp(proto, "pt"))
+       return FALSE;
+
+    twitter_debug("params=%p\n", params);
+
+    acct_id = purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER);
+    twitter_debug("acct_id=%s\n", acct_id);
+
+    if(strstr(cmd, "reply-twitter")) {
+        sender = g_hash_table_lookup(params, "user");
+        idstr = g_hash_table_lookup(params, "id");
+        if(idstr)
+            msgid = strtoull(idstr, NULL, 10);
+
+        /* find conv */
+        conv = purple_find_conversation_with_account(
+            PURPLE_CONV_TYPE_ANY, "twitter@twitter.com",
+            account_for_twitter); /* xxx */
+        twitter_debug("conv = %p\n", conv);
+        gtkconv = PIDGIN_CONVERSATION(conv);
+
+        twitter_debug("sender = %s, id = %llu\n", sender, (unsigned long long)msgid);
+
+        recipient = g_strdup_printf("@%s ", sender);
+        gtk_text_buffer_insert_at_cursor(gtkconv->entry_buffer,
+                                         recipient, -1);
+
+        gtk_widget_grab_focus(GTK_WIDGET(gtkconv->entry));
+        g_free(recipient);
+        reply_to_msgid = msgid; /* xxx */
+
+        return TRUE;
+    }
+    else if(strstr(cmd, "fav-twitter")) {
+        idstr = g_hash_table_lookup(params, "id");
+        fav_with_api(strtoull(idstr, NULL, 10));
+        return TRUE;
+    }
+    else if(strstr(cmd, "retweet-twitter")) {
+        sender = g_hash_table_lookup(params, "user");
+        idstr = g_hash_table_lookup(params, "id");
+        msg = g_hash_table_lookup(params, "msg");
+        if(idstr)
+            msgid = strtoull(idstr, NULL, 10);
+
+        /* find conv */
+        conv = purple_find_conversation_with_account(
+            PURPLE_CONV_TYPE_ANY, "twitter@twitter.com",
+            account_for_twitter); /* xxx */
+        twitter_debug("conv = %p\n", conv);
+        gtkconv = PIDGIN_CONVERSATION(conv);
+
+        twitter_debug("sender = %s, id = %llu\n", sender, (unsigned long long)msgid);
+
+        recipient = g_strdup_printf("RT @%s: %s", sender, msg);
+        gtk_text_buffer_insert_at_cursor(gtkconv->entry_buffer,
+                                         recipient, -1);
+
+        GtkTextIter iter;
+        gtk_text_buffer_get_start_iter(gtkconv->entry_buffer, &iter);
+        gtk_text_buffer_place_cursor(gtkconv->entry_buffer, &iter);
+
+        gtk_widget_grab_focus(GTK_WIDGET(gtkconv->entry));
+        g_free(recipient);
+
+        return TRUE;
+    }
+    return FALSE;
+}
+
+void
+twitter_add_links(gchar **str)
+{
+    GMatchInfo *match_info = NULL;
+    gchar *tmpstr0 = NULL, *tmpstr = NULL;
+    gchar *newstr = NULL, *match = NULL;
+    gchar *linkstr = NULL;
+    gchar *user = NULL;
+
+    twitter_debug("called\n");
+
+    /* buffer without ptmsgid=123 */
+    tmpstr0 = g_regex_replace(regp[SENDER], *str, -1, 0, "", 0, NULL);
+    tmpstr = g_regex_replace(regp[MESSAGE_ID], tmpstr0, -1, 0, "", 0, NULL);
+    g_free(tmpstr0);
+    tmpstr0 = NULL;
+    twitter_debug("tmpstr = %s\n", tmpstr);
+
+    /* sender */
+    g_regex_match(regp[SENDER], *str, 0, &match_info);
+    if(g_match_info_matches(match_info)) {
+        user = g_match_info_fetch(match_info, 2);
+        twitter_debug("user = %s\n", user);
+        g_match_info_free(match_info);
+        match_info = NULL;
+    }
+
+    /* link string */
+    g_regex_match(regp[MESSAGE_ID], *str, 0, &match_info);
+    if(match_info) {
+        match = g_match_info_fetch(match_info, 1);
+        linkstr = g_strdup_printf(LINK_FORMAT_TWITTER,
+                                 match, user,          /* Reply */
+                                 match,                /* Favorite */
+                                 match, user, tmpstr); /* Retweet */
+
+        twitter_debug("linkstr = %s\n", linkstr);
+
+        newstr = g_regex_replace(regp[MESSAGE_ID], *str, -1, 0,
+                                 linkstr,
+                                 0, NULL);
+
+        twitter_debug("newstr = %s\n", newstr);
+
+        g_free(*str);
+        *str = newstr;
+
+        g_free(linkstr);
+
+        g_free(match);
+        match = NULL;
+
+        g_match_info_free(match_info);
+        match_info = NULL;
+    }
+
+    g_free(user);
+    g_free(tmpstr);
+}
--- a/util.h	Mon Oct 12 01:45:36 2009 +0900
+++ b/util.h	Mon Oct 12 21:51:13 2009 +0900
@@ -5,6 +5,7 @@
 void strip_markup(gchar **str, gboolean escape);
 gchar *strip_html_markup(const gchar *src);
 gboolean ensure_path_exists(const char *dir);
+void twitter_add_links(gchar **str);
 
 gboolean is_twitter_conv(PurpleConversation *conv);
 gboolean is_wassr_account(PurpleAccount *account, const char *name);