Workflows: support CR variable substitution

Bug: twpowertools:91
Change-Id: Ib973bef40bed42d9c75f15710fd2ac3eeb6b9b15
diff --git a/src/contentScripts/communityConsole/workflows/actionRunners/replyWithCR.js b/src/contentScripts/communityConsole/workflows/actionRunners/replyWithCR.js
index e8f665c..e534ef9 100644
--- a/src/contentScripts/communityConsole/workflows/actionRunners/replyWithCR.js
+++ b/src/contentScripts/communityConsole/workflows/actionRunners/replyWithCR.js
@@ -6,6 +6,8 @@
 const kType_RecommendedAnswer = 3;
 const kPostMethodCommunityConsole = 4;
 
+const kVariablesRegex = /\$([A-Za-z_]+)/g;
+
 export default class CRRunner {
   constructor() {
     this._CRs = [];
@@ -36,31 +38,43 @@
     });
   }
 
+  _templateSubstitute(payload, thread) {
+    if (!payload.match(kVariablesRegex)) return Promise.resolve(payload);
+
+    return thread.loadThreadDetails().then(() => {
+      return payload.replaceAll(kVariablesRegex, (_, p1) => {
+        return thread?.[p1] ?? '';
+      });
+    });
+  }
+
   execute(action, thread) {
     let crId = action?.getCannedResponseId?.();
     if (!crId)
       return Promise.reject(
           new Error('The action doesn\'t contain a valid CR id.'));
 
-    return this._getCRPayload(crId).then(payload => {
-      let subscribe = action?.getSubscribe?.() ?? false;
-      let markAsAnswer = action?.getMarkAsAnswer?.() ?? false;
-      return CCApi(
-          'CreateMessage', {
-            1: thread.forum,   // forumId
-            2: thread.thread,  // threadId
-            // message
-            3: {
-              4: payload,
-              6: {
-                1: markAsAnswer ? kType_RecommendedAnswer : kType_Reply,
+    return this._getCRPayload(crId)
+        .then(payload => this._templateSubstitute(payload, thread))
+        .then(payload => {
+          let subscribe = action?.getSubscribe?.() ?? false;
+          let markAsAnswer = action?.getMarkAsAnswer?.() ?? false;
+          return CCApi(
+              'CreateMessage', {
+                1: thread.forumId,
+                2: thread.threadId,
+                // message
+                3: {
+                  4: payload,
+                  6: {
+                    1: markAsAnswer ? kType_RecommendedAnswer : kType_Reply,
+                  },
+                  11: kPostMethodCommunityConsole,
+                },
+                4: subscribe,
+                6: kPiiScanType_ScanNone,
               },
-              11: kPostMethodCommunityConsole,
-            },
-            4: subscribe,
-            6: kPiiScanType_ScanNone,
-          },
-          /* authenticated = */ true, getAuthUser());
-    });
+              /* authenticated = */ true, getAuthUser());
+        });
   }
 }