Add threadListAvatars experiment

This experiment adds avatars of the users who have participated in a
thread in the thread list, next to each thread.

Change-Id: I259b103a7d3462201013ab2027866bbcce476901
diff --git a/src/content_scripts/console_inject.js b/src/content_scripts/console_inject.js
index c042f92..ae10881 100644
--- a/src/content_scripts/console_inject.js
+++ b/src/content_scripts/console_inject.js
@@ -13,6 +13,20 @@
   return getNParent(node.parentNode, n - 1);
 }
 
+function parseUrl(url) {
+  var forum_a = url.match(/forum\/([0-9]+)/i);
+  var thread_a = url.match(/thread\/([0-9]+)/i);
+
+  if (forum_a === null || thread_a === null) {
+    return false;
+  }
+
+  return {
+    'forum': forum_a[1],
+    'thread': thread_a[1],
+  };
+}
+
 function createExtBadge() {
   var badge = document.createElement('div');
   badge.classList.add('TWPT-badge');
@@ -221,6 +235,93 @@
       clone, (readToggle.nextSibling || readToggle));
 }
 
+// TODO(avm99963): This is a prototype. DON'T FORGET TO ADD ERROR HANDLING.
+function injectAvatars(node) {
+  var header = node.querySelector(
+      'ec-thread-summary .main-header .panel-description a.header');
+  if (header === null) return;
+
+  var link = parseUrl(header.href);
+  if (link === false) return;
+
+  var APIRequestUrl = 'https://support.google.com/s/community/api/ViewThread' +
+      (authuser == '0' ? '' : '?authuser=' + encodeURIComponent(authuser));
+
+  fetch(APIRequestUrl, {
+    'headers': {
+      'content-type': 'text/plain; charset=utf-8',
+    },
+    'body': JSON.stringify({
+      1: link.forum,
+      2: link.thread,
+      3: {
+        1: {2: 15},
+        3: true,
+        5: true,
+        10: true,
+        16: true,
+        18: true,
+      }
+    }),
+    'method': 'POST',
+    'mode': 'cors',
+    'credentials': 'omit',
+  })
+      .then(res => {
+        if (res.status == 200 || res.status == 400) {
+          return res.json().then(data => ({
+                                   status: res.status,
+                                   body: data,
+                                 }));
+        } else {
+          throw new Error('Status code ' + res.status + ' was not expected.');
+        }
+      })
+      .then(res => {
+        if (res.status == 400) {
+          throw new Error(
+              res.body[4] ||
+              ('Response status: 400. Error code: ' + res.body[2]));
+        }
+
+        return res.body;
+      })
+      .then(data => {
+        if (!('1' in data) || !('8' in data['1'])) return false;
+
+        var messages = data['1']['8'];
+        if (messages == 0) return;
+
+        var avatarUrls = [];
+
+        if (!('3' in data['1'])) return false;
+        for (var m of data['1']['3']) {
+          if (!('3' in m) || !('1' in m['3']) || !('2' in m['3']['1']))
+            continue;
+
+          var url = m['3']['1']['2'];
+
+          if (!avatarUrls.includes(url)) avatarUrls.push(url);
+
+          if (avatarUrls.length == 3) break;
+        }
+
+        var avatarsContainer = document.createElement('div');
+        avatarsContainer.classList.add('TWPT-avatars');
+
+        var count = Math.floor(Math.random() * 4);
+
+        for (var i = 0; i < avatarUrls.length; ++i) {
+          var avatar = document.createElement('div');
+          avatar.classList.add('TWPT-avatar');
+          avatar.style.backgroundImage = 'url(\''+avatarUrls[i]+'\')';
+          avatarsContainer.appendChild(avatar);
+        }
+
+        header.appendChild(avatarsContainer);
+      });
+}
+
 function injectPreviousPostsLinks(nameElement) {
   var mainCardContent = getNParent(nameElement, 3);
   if (mainCardContent === null) {
@@ -272,6 +373,9 @@
   // Read/unread bulk action in the list of thread, for the batch lock feature
   'ec-bulk-actions material-button[debugid="mark-read-button"]',
   'ec-bulk-actions material-button[debugid="mark-unread-button"]',
+
+  // Thread list items (used to inject the avatars)
+  'li',
 ];
 
 function handleCandidateNode(node) {
@@ -339,6 +443,13 @@
     if (options.batchlock && nodeIsReadToggleBtn(node)) {
       addBatchLockBtn(node);
     }
+
+    // Inject avatar links to threads in the thread list
+    if (options.threadlistavatars && ('tagName' in node) &&
+        (node.tagName == 'LI') &&
+        node.querySelector('ec-thread-summary') !== null) {
+      injectAvatars(node);
+    }
   }
 }
 
@@ -417,4 +528,9 @@
   if (options.batchlock) {
     injectScript(chrome.runtime.getURL('injections/batchlock_inject.js'));
   }
+
+  if (options.threadlistavatars) {
+    injectStylesheet(
+        chrome.runtime.getURL('injections/thread_list_avatars.css'));
+  }
 });