Repository: mbrt/gmailctl
Pull request #199 (2021-09-25 14:27:57)

Add Reply-To function
Adding Reply-To function mentioned in issue https://github.com/mbrt/gmailctl/issues/140

mbrt: Thanks a lot for the contribution!

I was trying to test this out, but I couldn't make it work.
It seems that Gmail doesn't support this at all? https://support.google.com/mail/answer/7190?hl=en.

mbrt: Actually I was wrong, it does seem to work, it's just undocumented.

I was able to create a filter that does just that and it seems to work.

FFR, here's the exported version:

```xml
<feed xmlns='http://www.w3.org/2005/Atom' xmlns:apps='http://schemas.google.com/apps/2006'>
	<title>Mail Filters</title>
	<id>tag:mail.google.com,2008:filters:...</id>
	<updated>2021-09-27T19:35:54Z</updated>
	<author>
		<name>Me</name>
		<email>me@gmail.com</email>
	</author>
	<entry>
		<category term='filter'></category>
		<title>Mail Filter</title>
		<id>tag:mail.google.com,2008:filter:z...</id>
		<updated>2021-09-27T19:35:54Z</updated>
		<content></content>
		<apps:property name='hasTheWord' value='replyto:mbrt/gmailctl'/>
		<apps:property name='shouldNeverSpam' value='true'/>
		<apps:property name='sizeOperator' value='s_sl'/>
		<apps:property name='sizeUnit' value='s_smb'/>
	</entry>
</feed>
peteroneilljr: Hi @mbrt was it as simple as updating the list of fields available for the test object? Or is there an actual test object that needs to be updated?
peteroneilljr: Alright, everything should be fixed now. I was updating all of the same files that were updated when BCC was added as an operator and  It seems I updated an autogenerated file used by the Go tests. 
mbrt: Sorry for the delay in responding.

Actually the file you updated was good, it's the input to the test. The result is checked against the JSON files that are updated through the commands in the [comment above](https://github.com/mbrt/gmailctl/pull/199#pullrequestreview-767549559).

If you remove it, there's no test for the new field you added, so a future change might break the behavior. Could you please add it back and fix the tests? Then we are truly good to go.
peteroneilljr: Ok I think the tests are fixed now! :) 

Patch:
diff --git a/README.md b/README.md
index 32897b7..7bef222 100644
--- a/README.md
+++ b/README.md
@@ -260,6 +260,7 @@ the following common operators:
 * `list`: the mail is directed to the given mail list
 * `cc`: the mail has the given address as CC destination
 * `bcc`: the mail has the given address as BCC destination
+* `replyto`: the mail has the given address as Reply-To destination
 
 One more special function is given if you need to use less common operators<sup
 id="a1">[1](#f1)</sup>, or want to compose your query manually:
@@ -595,6 +596,7 @@ as possible. This is the list of fields:
 * `to: [<list>]`: a list of recipients of the email.
 * `cc: [<list>]`: a list of emails in cc.
 * `bcc: [<list>]`: a list of emails in bcc.
+* `replyto: <string>`: the email listed in the Reply-To field.
 * `lists: [<list>]`: a list of mailing lists.
 * `subject: <string>`: the subject of the email.
 * `body: <string>`: the body of the email.
diff --git a/pkg/config/v1alpha3/config.go b/pkg/config/v1alpha3/config.go
index 72de9d4..646fe7e 100644
--- a/pkg/config/v1alpha3/config.go
+++ b/pkg/config/v1alpha3/config.go
@@ -35,6 +35,7 @@ type FilterNode struct {
 	To      string `json:"to,omitempty"`
 	Cc      string `json:"cc,omitempty"`
 	Bcc     string `json:"bcc,omitempty"`
+	ReplyTo string `json:"replyto,omitempty"`
 	Subject string `json:"subject,omitempty"`
 	List    string `json:"list,omitempty"`
 	Has     string `json:"has,omitempty"`
@@ -182,6 +183,7 @@ type Message struct {
 	To      []string `json:"to,omitempty"`
 	Cc      []string `json:"cc,omitempty"`
 	Bcc     []string `json:"bcc,omitempty"`
+	ReplyTo []string `json:"replyto,omitempty"`
 	Lists   []string `json:"lists,omitempty"`
 	Subject string   `json:"subject,omitempty"`
 	Body    string   `json:"body,omitempty"`
diff --git a/pkg/filter/convert.go b/pkg/filter/convert.go
index 39ca992..0f01752 100644
--- a/pkg/filter/convert.go
+++ b/pkg/filter/convert.go
@@ -133,6 +133,10 @@ func generateLeaf(leaf *parser.Leaf) (Criteria, error) {
 		return Criteria{
 			Query: fmt.Sprintf("bcc:%s", query),
 		}, nil
+	case parser.FunctionReplyTo:
+		return Criteria{
+			Query: fmt.Sprintf("replyto:%s", query),
+		}, nil
 	case parser.FunctionList:
 		return Criteria{
 			Query: fmt.Sprintf("list:%s", query),
diff --git a/pkg/parser/ast.go b/pkg/parser/ast.go
index 5ae0864..369f9c6 100644
--- a/pkg/parser/ast.go
+++ b/pkg/parser/ast.go
@@ -35,6 +35,7 @@ const (
 	FunctionTo
 	FunctionCc
 	FunctionBcc
+	FunctionReplyTo
 	FunctionSubject
 	FunctionList
 	FunctionHas
@@ -56,6 +57,8 @@ func (f FunctionType) String() string {
 		return "cc"
 	case FunctionBcc:
 		return "bcc"
+	case FunctionReplyTo:
+		return "replyto"
 	case FunctionSubject:
 		return "subject"
 	case FunctionList:
diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go
index 6dc1d6a..1603f6c 100644
--- a/pkg/parser/parser.go
+++ b/pkg/parser/parser.go
@@ -140,6 +140,9 @@ func parseFunction(f cfg.FilterNode) (FunctionType, string) {
 	if f.Bcc != "" {
 		return FunctionBcc, f.Bcc
 	}
+	if f.ReplyTo != "" {
+		return FunctionReplyTo, f.ReplyTo
+	}
 	if f.Subject != "" {
 		return FunctionSubject, f.Subject
 	}


Test patch:
diff --git a/pkg/cfgtest/builder.go b/pkg/cfgtest/builder.go
index 2755d35..33f6557 100644
--- a/pkg/cfgtest/builder.go
+++ b/pkg/cfgtest/builder.go
@@ -55,7 +55,7 @@ func (r *evalBuilder) VisitLeaf(n *parser.Leaf) {
 	var rules []RuleEvaluator
 
 	switch n.Function {
-	case parser.FunctionFrom, parser.FunctionCc, parser.FunctionBcc, parser.FunctionList:
+	case parser.FunctionFrom, parser.FunctionCc, parser.FunctionBcc, parser.FunctionList, parser.FunctionReplyTo:
 		rules = expandAll(n.Args, func(a string) RuleEvaluator {
 			return emailField(toMatchField(n.Function), a)
 		})
@@ -179,6 +179,8 @@ func toMatchField(f parser.FunctionType) matchField {
 		return matchFieldCc
 	case parser.FunctionBcc:
 		return matchFieldBcc
+	case parser.FunctionReplyTo:
+		return matchFieldReplyTo
 	case parser.FunctionList:
 		return matchFieldLists
 	case parser.FunctionSubject:
diff --git a/pkg/cfgtest/eval.go b/pkg/cfgtest/eval.go
index 6a9eb7e..3fb0290 100644
--- a/pkg/cfgtest/eval.go
+++ b/pkg/cfgtest/eval.go
@@ -22,6 +22,7 @@ const (
 	matchFieldTo
 	matchFieldCc
 	matchFieldBcc
+	matchFieldReplyTo
 	matchFieldLists
 	matchFieldSubject
 	matchFieldBody
@@ -86,6 +87,8 @@ func (n funcNode) Match(msg cfg.Message) bool {
 		fields = msg.Cc
 	case matchFieldBcc:
 		fields = msg.Bcc
+	case matchFieldReplyTo:
+		fields = msg.ReplyTo
 	case matchFieldLists:
 		fields = msg.Lists
 	case matchFieldSubject:
diff --git a/pkg/config/testdata/chainfilters.json b/pkg/config/testdata/chainfilters.json
index f91eddf..e4c2320 100644
--- a/pkg/config/testdata/chainfilters.json
+++ b/pkg/config/testdata/chainfilters.json
@@ -83,6 +83,21 @@
           "mylist"
         ]
       }
+    },
+    {
+      "filter": {
+        "and": [
+          {
+            "from": "Google Docs"
+          },
+          {
+            "replyto": "pippo@gmail.com"
+          }
+        ]
+      },
+      "actions": {
+        "star": true
+      }
     }
   ]
 }
\ No newline at end of file
diff --git a/pkg/config/testdata/chainfilters.jsonnet b/pkg/config/testdata/chainfilters.jsonnet
index 9aa86fc..50fc1c2 100644
--- a/pkg/config/testdata/chainfilters.jsonnet
+++ b/pkg/config/testdata/chainfilters.jsonnet
@@ -25,6 +25,21 @@ local ruleC = {
     labels: ['mylist'],
   },
 };
+local ruleD = {
+  filter: {
+    and: [
+      {
+        from: 'Google Docs'
+        },
+      {
+        replyto: me,
+      },
+    ],
+  },
+  actions: {
+      star: true,
+    },
+};
 
 {
   version: 'v1alpha3',
@@ -41,6 +56,8 @@ local ruleC = {
   + lib.chainFilters([])
   // Single rule should not err
   + lib.chainFilters([ruleB])
-  // Chain all 3
+  // Chain 3 rules
   + lib.chainFilters([ruleA, ruleB, ruleC])
+  // Checks replyto is different than from
+  + lib.chainFilters([ruleD])
 }