Bug 955642 - Handle Twitter direct messages (DMs) - Pull out shared methods from TimelineConversation, r=clokep.
authorArlo Breault <arlolra@gmail.com>
Wed, 06 Jul 2016 12:41:00 -0400
changeset 26509 de9e1f18b4d9ac07c347a15e799b00238f1d98f3
parent 26508 803b13b2bf6097d8a22a84b67f4bbda056af19df
child 26510 050354367d676eb476cf340609d926b6e7abf34d
push id353
push userclokep@gmail.com
push dateMon, 23 Jan 2017 21:39:40 +0000
reviewersclokep
bugs955642
Bug 955642 - Handle Twitter direct messages (DMs) - Pull out shared methods from TimelineConversation, r=clokep.
chat/protocols/twitter/twitter.js
--- a/chat/protocols/twitter/twitter.js
+++ b/chat/protocols/twitter/twitter.js
@@ -118,124 +118,37 @@ function Action(aLabel, aAction, aTweet)
   this._action = aAction;
   this._tweet = aTweet;
 }
 Action.prototype = {
   __proto__: ClassInfo("prplIMessageAction", "generic message action object"),
   get run() { return this._action.bind(this._tweet); }
 };
 
-function Conversation(aAccount)
-{
-  this._init(aAccount);
-  this._ensureParticipantExists(aAccount.name);
-  // We need the screen names for the IDs in _friends, but _userInfo is
-  // indexed by name, so we build an ID -> name map.
-  let entries = [];
-  for (let [name, userInfo] of aAccount._userInfo) {
-    entries.push([userInfo.id_str, name]);
-  }
-  let names = new Map(entries);
-  for (let id_str of aAccount._friends)
-    this._ensureParticipantExists(names.get(id_str));
-
-  // If the user's info has already been received, update the timeline topic.
-  if (aAccount._userInfo.has(aAccount.name)) {
-    let userInfo = aAccount._userInfo.get(aAccount.name);
-    if ("description" in userInfo)
-      this.setTopic(userInfo.description, aAccount.name, true);
-  }
-}
-Conversation.prototype = {
-  __proto__: GenericConvChatPrototype,
-  unInit: function() {
-    delete this._account._timeline;
-    GenericConvChatPrototype.unInit.call(this);
-  },
-  inReplyToStatusId: null,
-  startReply: function(aTweet) {
-    this.inReplyToStatusId = aTweet.id_str;
-    let entities = aTweet.entities;
-
-    // Twitter replies go to all the users mentioned in the tweet.
-    let nicks = [aTweet.user.screen_name];
-    if ("user_mentions" in entities && Array.isArray(entities.user_mentions)) {
-      nicks = nicks.concat(entities.user_mentions
-                                   .map(um => um.screen_name));
-    }
-    // Ignore duplicates and the user's nick.
-    let prompt =
-      nicks.filter(function(aNick, aPos) {
-             return nicks.indexOf(aNick) == aPos && aNick != this._account.name;
-           }, this)
-           .map(aNick => "@" + aNick)
-           .join(" ") + " ";
-
-    this.notifyObservers(null, "replying-to-prompt", prompt);
-    this.notifyObservers(null, "status-text-changed",
-                         _("replyingToStatusText", aTweet.text));
-  },
-  reTweet: function(aTweet) {
-    this._account.reTweet(aTweet, this.onSentCallback,
-                          function(aException, aData) {
-      this.systemMessage(_("error.retweet", this._parseError(aData),
-                           aTweet.text), true);
-    }, this);
-  },
+// Properties / methods shared by both DirectMessageConversation and
+// TimelineConversation.
+var GenericTwitterConversation = {
   getTweetLength: function (aString) {
     // Use the Twitter library to calculate the length.
     return twttr.txt.getTweetLength(aString, this._account.config);
   },
-  sendMsg: function (aMsg) {
-    if (this.getTweetLength(aMsg) > kMaxMessageLength) {
-      this.systemMessage(_("error.tooLong"), true);
-      throw Cr.NS_ERROR_INVALID_ARG;
-    }
-    this._account.tweet(aMsg, this.inReplyToStatusId, this.onSentCallback,
-                        function(aException, aData) {
-      let error = this._parseError(aData);
-      this.systemMessage(_("error.general", error, aMsg), true);
-    }, this);
-    this.sendTyping("");
-  },
-  sendTyping: function(aString) {
-    if (aString.length == 0 && this.inReplyToStatusId) {
-      delete this.inReplyToStatusId;
-      this.notifyObservers(null, "status-text-changed", "");
-      return kMaxMessageLength;
-    }
-    return kMaxMessageLength - this.getTweetLength(aString);
-  },
   systemMessage: function(aMessage, aIsError, aDate) {
     let flags = {system: true};
     if (aIsError)
       flags.error = true;
     if (aDate)
       flags.time = aDate;
     this.writeMessage("twitter.com", aMessage, flags);
   },
   onSentCallback: function(aData) {
     let tweet = JSON.parse(aData);
     if (tweet.user.screen_name != this._account.name)
       throw "Wrong screen_name... Uh?";
     this._account.displayMessages([tweet]);
   },
-  _parseError: function(aData) {
-    let error = "";
-    try {
-      let data = JSON.parse(aData);
-      if ("error" in data)
-        error = data.error;
-      else if ("errors" in data)
-        error = data.errors[0].message;
-      if (error)
-        error = "(" + error + ")";
-    } catch(e) {}
-    return error;
-  },
   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. In this case, we want to get
     // the FULL text from the original tweet and update the entities to match.
@@ -357,16 +270,108 @@ Conversation.prototype = {
       flags.delayed = true;
     if (aTweet.entities && aTweet.entities.user_mentions &&
         Array.isArray(aTweet.entities.user_mentions) &&
         aTweet.entities.user_mentions.some(mention => mention.screen_name == this.nick))
       flags.containsNick = true;
 
     (new Tweet(aTweet, name, text, flags)).conversation = this;
   },
+  _parseError: function(aData) {
+    let error = "";
+    try {
+      let data = JSON.parse(aData);
+      if ("error" in data)
+        error = data.error;
+      else if ("errors" in data)
+        error = data.errors[0].message;
+      if (error)
+        error = "(" + error + ")";
+    } catch(e) {}
+    return error;
+  }
+};
+
+function TimelineConversation(aAccount)
+{
+  this._init(aAccount);
+  this._ensureParticipantExists(aAccount.name);
+  // We need the screen names for the IDs in _friends, but _userInfo is
+  // indexed by name, so we build an ID -> name map.
+  let entries = [];
+  for (let [name, userInfo] of aAccount._userInfo) {
+    entries.push([userInfo.id_str, name]);
+  }
+  let names = new Map(entries);
+  for (let id_str of aAccount._friends)
+    this._ensureParticipantExists(names.get(id_str));
+
+  // If the user's info has already been received, update the timeline topic.
+  if (aAccount._userInfo.has(aAccount.name)) {
+    let userInfo = aAccount._userInfo.get(aAccount.name);
+    if ("description" in userInfo)
+      this.setTopic(userInfo.description, aAccount.name, true);
+  }
+}
+TimelineConversation.prototype = {
+  __proto__: GenericConvChatPrototype,
+  unInit: function() {
+    delete this._account._timeline;
+    GenericConvChatPrototype.unInit.call(this);
+  },
+  inReplyToStatusId: null,
+  startReply: function(aTweet) {
+    this.inReplyToStatusId = aTweet.id_str;
+    let entities = aTweet.entities;
+
+    // Twitter replies go to all the users mentioned in the tweet.
+    let nicks = [aTweet.user.screen_name];
+    if ("user_mentions" in entities && Array.isArray(entities.user_mentions)) {
+      nicks = nicks.concat(entities.user_mentions
+                                   .map(um => um.screen_name));
+    }
+    // Ignore duplicates and the user's nick.
+    let prompt =
+      nicks.filter(function(aNick, aPos) {
+             return nicks.indexOf(aNick) == aPos && aNick != this._account.name;
+           }, this)
+           .map(aNick => "@" + aNick)
+           .join(" ") + " ";
+
+    this.notifyObservers(null, "replying-to-prompt", prompt);
+    this.notifyObservers(null, "status-text-changed",
+                         _("replyingToStatusText", aTweet.text));
+  },
+  reTweet: function(aTweet) {
+    this._account.reTweet(aTweet, this.onSentCallback,
+                          function(aException, aData) {
+      this.systemMessage(_("error.retweet", this._parseError(aData),
+                           aTweet.text), true);
+    }, this);
+  },
+  sendMsg: function (aMsg) {
+    if (this.getTweetLength(aMsg) > kMaxMessageLength) {
+      this.systemMessage(_("error.tooLong"), true);
+      throw Cr.NS_ERROR_INVALID_ARG;
+    }
+    this._account.tweet(aMsg, this.inReplyToStatusId, this.onSentCallback,
+                        function(aException, aData) {
+      let error = this._parseError(aData);
+      this.systemMessage(_("error.general", error, aMsg), true);
+    }, this);
+    this.sendTyping("");
+  },
+  sendTyping: function(aString) {
+    if (aString.length == 0 && this.inReplyToStatusId) {
+      delete this.inReplyToStatusId;
+      this.notifyObservers(null, "status-text-changed", "");
+      return kMaxMessageLength;
+    }
+    return kMaxMessageLength - this.getTweetLength(aString);
+  },
   _ensureParticipantExists: function(aNick) {
     if (this._participants.has(aNick))
       return;
 
     let chatBuddy = new ChatBuddy(aNick, this._account);
     this._participants.set(aNick, chatBuddy);
     this.notifyObservers(new nsSimpleEnumerator([chatBuddy]),
                          "chat-buddy-add");
@@ -377,16 +382,17 @@ Conversation.prototype = {
   set nick(aNick) {},
   get topicSettable() { return this.nick == this._account.name; },
   get topic() { return this._topic; }, // can't add a setter without redefining the getter
   set topic(aTopic) {
     if (this.topicSettable)
       this._account.setUserDescription(aTopic);
   }
 };
+Object.assign(TimelineConversation.prototype, GenericTwitterConversation);
 
 function Account(aProtocol, aImAccount)
 {
   this._init(aProtocol, aImAccount);
   this._knownMessageIds = new Set();
   this._userInfo = new Map();
   this._friends = new Set();
 }
@@ -616,17 +622,17 @@ Account.prototype = {
       getParams = "?q=" + trackQuery + lastMsgParam + "&count=100";
       let url = "1.1/search/tweets.json" + getParams;
       this._pendingRequests.push(
         this.signAndSend(url, null, null, this.onTimelineReceived,
                          this.onTimelineError, this, null));
     }
   },
 
-  get timeline() { return this._timeline || (this._timeline = new Conversation(this)); },
+  get timeline() { return this._timeline || (this._timeline = new TimelineConversation(this)); },
   displayMessages: function(aMessages) {
     let lastMsgId = this._lastMsgId;
     for (let tweet of aMessages) {
       if (!("user" in tweet) || !("text" in tweet) || !("id_str" in tweet) ||
           this._knownMessageIds.has(tweet.id_str))
         continue;
       let id = tweet.id_str;
       // Update the last known message.