Bug 954118 - Handle deleting messages on twitter, r=florian.
authorPatrick Cloke <clokep@gmail.com>
Sun, 15 Jan 2012 17:04:40 +0100
changeset 18513 798d0f7e6171b3d67d7491b7e98b6d8cbd02160f
parent 18512 53359932c156375ae2b23b14351281caf73d74ef
child 18514 d2e0b992749dd1b5724a1bb60844e786f0ff9786
push id1103
push usermbanner@mozilla.com
push dateTue, 18 Mar 2014 07:44:06 +0000
treeherdercomm-beta@50c6279a0af0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersflorian
bugs954118
Bug 954118 - Handle deleting messages on twitter, r=florian.
chat/locales/en-US/twitter.properties
chat/protocols/twitter/twitter.js
--- a/chat/locales/en-US/twitter.properties
+++ b/chat/locales/en-US/twitter.properties
@@ -3,38 +3,43 @@
 error.tooLong=Status is over 140 characters.
 # LOCALIZATION NOTE (error.general, error.retweet)
 #   The first %S will be either the error string returned by the twitter server,
 #   in English, inside parenthesis, or the empty string if we have no specific
 #   message for the error.
 #   The second %S is the message that caused the error.
 error.general=An error %S occurred while sending: %S
 error.retweet=An error %S occurred while retweeting: %S
+error.delete=An error %S occurred while deleting: %S
 
 # LOCALIZATION NOTE
 #   This is the title of the conversation tab, %S will be replaced by
 #   @<username>.
 timeline=%S timeline
 
 # LOCALIZATION NOTE
 #  This will be an action in the context menu of displayed tweets.
 action.copyLink=Copy Link to Tweet
 action.retweet=Retweet
 action.reply=Reply
+action.delete=Delete
 # LOCALIZATION NOTE
 #  %S will be replaced by the screen name of a twitter user.
 action.follow=Follow %S
 action.stopFollowing=Stop following %S
 
 # LOCALIZATION NOTE
 #  This will be displayed in system messages inside the timeline conversation.
 #  %S will be replaced by the screen name of a twitter user.
 event.follow=You are now following %S.
 event.unfollow=You are no longer following %S.
 event.followed=%S is now following you.
+# LOCALIZATION NOTE
+#  %S will be replaced by the text of the deleted tweet.
+event.deleted=You have deleted this tweet: "%S".
 
 # LOCALIZATION NOTE
 #  This will be visible in the status bar of the conversation window
 #  while the user is typing a reply to a tweet.
 #  %S will be replaced by the text of the tweet the user is replying to.
 replyingToStatusText=Replying to: %S
 
 # LOCALIZATION NOTE (connection.*)
--- a/chat/protocols/twitter/twitter.js
+++ b/chat/protocols/twitter/twitter.js
@@ -58,16 +58,17 @@ ChatBuddy.prototype = GenericConvChatBud
 
 function Tweet(aTweet, aWho, aMessage, aObject)
 {
   this._tweet = aTweet;
   this._init(aWho, aMessage, aObject);
 }
 Tweet.prototype = {
   __proto__: GenericMessagePrototype,
+  _deleted: false,
   getActions: function(aCount) {
     let account = this.conversation._account;
     if (!account.connected) {
       if (aCount)
         aCount.value = 0;
       return [];
     }
 
@@ -85,25 +86,55 @@ Tweet.prototype = {
       let friend = account.isFriend(this._tweet.user);
       if (friend !== null) {
         let action = friend ? "stopFollowing" : "follow";
         let screenName = this._tweet.user.screen_name;
         actions.push(new Action(_("action." + action, screenName),
                                 function() { account[action](screenName); }));
       }
     }
+    else if (this.outgoing && !this._deleted) {
+      actions.push(
+        new Action(_("action.delete"), function() {
+          this.destroy();
+        }, this)
+      );
+    }
     actions.push(new Action(_("action.copyLink"), function() {
       let href = "https://twitter.com/#!/" + this._tweet.user.screen_name +
                  "/status/" + this._tweet.id_str;
       Cc["@mozilla.org/widget/clipboardhelper;1"]
         .getService(Ci.nsIClipboardHelper).copyString(href);
     }, this));
     if (aCount)
       aCount.value = actions.length;
     return actions;
+  },
+  destroy: function() {
+    // Mark the tweet as deleted until we receive a response.
+    this._deleted = true;
+
+    this.conversation._account.destroy(this._tweet, this.onDestroyCallback,
+                                       this.onDestroyErrorCallback, this);
+  },
+  onDestroyErrorCallback: function(aException, aData) {
+    // The tweet was not successfully deleted.
+    delete this._deleted;
+    let error = this.conversation._parseError(aData);
+    this.conversation.systemMessage(_("error.delete", error,
+                                      this.originalMessage), true);
+  },
+  onDestroyCallback: function(aData) {
+    let tweet = JSON.parse(aData);
+    // If Twitter responds with an error, throw to call the error callback.
+    if ("error" in tweet)
+      throw tweet.error;
+
+    // Create a new system message saying the tweet has been deleted.
+    this.conversation.systemMessage(_("event.deleted", this.originalMessage));
   }
 };
 
 function Action(aLabel, aAction, aTweet)
 {
   this.label = aLabel;
   this._action = aAction;
   this._tweet = aTweet;
@@ -180,17 +211,17 @@ Conversation.prototype = {
         error = data.error;
       else if ("errors" in data)
         error = data.errors.split("\n")[0];
       if (error)
         error = "(" + error + ")";
     } catch(e) {}
     return error;
   },
-  displayTweet: function(aTweet) {
+  parseTweet: function(aTweet) {
     let text = aTweet.text;
     let entities = {};
     // Handle retweets: retweeted_status contains the object for the original
     // tweet that is being retweeted.
     // If the retweet prefix ("RT @<username>: ") causes the tweet to be over
     // 140 characters, ellipses will be added (and the truncated property is set
     // to true). In this case, we want to get the FULL text from the original
     // tweet and update the entities to match.
@@ -233,19 +264,16 @@ Conversation.prototype = {
         }
       }
     } else {
       // For non-retweets, we just want to use the entities that are given.
       if ("entities" in aTweet)
         entities = aTweet.entities;
     }
 
-    let name = aTweet.user.screen_name;
-    this._ensureParticipantExists(name);
-
     if (Object.keys(entities).length) {
       /* entArray is an array of entities ready to be replaced in the tweet,
        * each entity contains:
        *  - start: the start index of the entity inside the tweet,
        *  - end: the end index of the entity inside the tweet,
        *  - str: the string that should be replaced inside the tweet,
        *  - href: the url (href attribute) of the created link tag,
        *  - [optional] text: the text to display for the link,
@@ -293,16 +321,23 @@ Conversation.prototype = {
           html += " title=\"" + entity.title + "\"";
         html += ">" + ("text" in entity ? entity.text : entity.str) + "</a>";
         text = text.slice(0, offset + entity.start) + html +
                text.slice(offset + entity.end);
         offset += html.length - (entity.end - entity.start);
       }
     }
 
+    return text;
+  },
+  displayTweet: function(aTweet) {
+    let name = aTweet.user.screen_name;
+    this._ensureParticipantExists(name);
+    let text = this.parseTweet(aTweet);
+
     let flags =
       name == this._account.name ? {outgoing: true} : {incoming: true};
     flags.time = Math.round(new Date(aTweet.created_at) / 1000);
     flags._iconURL = aTweet.user.profile_image_url;
     if (text.indexOf(this.nick) != -1)
       flags.containsNick = true;
 
     (new Tweet(aTweet, name, text, flags)).conversation = this;
@@ -462,16 +497,21 @@ Account.prototype = {
     this.signAndSend("1/statuses/update.json?include_entities=1", null,
                      POSTData, aOnSent, aOnError, aThis);
   },
   reTweet: function(aTweet, aOnSent, aOnError, aThis) {
     let url =
       "1/statuses/retweet/" + aTweet.id_str + ".json?include_entities=1";
     this.signAndSend(url, null, [], aOnSent, aOnError, aThis);
   },
+  destroy: function(aTweet, aOnSent, aOnError, aThis) {
+    let url =
+      "1/statuses/destroy/" + aTweet.id_str + ".json?include_entities=1";
+    this.signAndSend(url, null, [], aOnSent, aOnError, aThis);
+  },
 
   _friends: null,
   isFriend: function(aUser) {
     if (!("id" in aUser) || // users from search API tweets don't have an id.
         !this._friends) // null until data is received from the user stream.
       return null;
     //XXX Good enough for now, but if we ever call this from a loop,
     // we should keep this._friends sorted and do a binary search.