Skip to content

Commit 5e69e72

Browse files
committed
Add feature for replies
1 parent a4f71eb commit 5e69e72

File tree

7 files changed

+144
-61
lines changed

7 files changed

+144
-61
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
.idea
2-
service/structure.json
2+
service/*.json
33
.env

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
# imap2jira
2-
This tool reads a mailbox and search for unseen mails. These mails are converted into JIRA tickets. You have to customize structure.json for your workflow.
2+
This tool reads a mailbox and search for unseen mails. These mails are converted into JIRA tickets. You have to customize structure_new_issue.json and structure_add_comment.json for your workflow.
33

4-
You can run this tool with docker (image and docker-compose are provided in this project)
4+
You can run this tool with docker (image and docker-compose are provided in this project)
5+
6+
### Mail without [Issue-1] in subject
7+
A new issue is generated for this mail. You want to set the issue number into the subject of your answer in format `[ISSUENUMBER]`
8+
9+
### Mail with [Issue-1] in subject
10+
If this issue exists a comment will added to this issue

docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ services:
1212
- .env
1313
volumes:
1414
- ./service:/go/src/app
15-
- ./structure.json:/go/src/app/structure.json
15+
- ./structure_new_issue.json:/go/src/app/structure_new_issue.json
16+
- ./structure_add_comment.json:/go/src/app/structure_add_comment.json

service/cmd/server/main.go

Lines changed: 131 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"github.com/emersion/go-imap"
77
"github.com/emersion/go-imap/client"
88
"github.com/emersion/go-message/mail"
9-
strip "github.com/grokify/html-strip-tags-go"
109
"github.com/microcosm-cc/bluemonday"
1110
"github.com/robfig/cron/v3"
1211
"io"
@@ -15,6 +14,7 @@ import (
1514
"net/http"
1615
"os"
1716
"os/signal"
17+
"regexp"
1818
"strings"
1919
)
2020

@@ -31,6 +31,76 @@ func main() {
3131

3232
}
3333

34+
func printErrorFromApi(resp *http.Response) {
35+
bodyBytes, err := ioutil.ReadAll(resp.Body)
36+
if err != nil {
37+
log.Fatal(err)
38+
}
39+
bodyString := string(bodyBytes)
40+
err = errors.New(bodyString)
41+
log.Print(err)
42+
}
43+
44+
func getMailBody(p *mail.Part) string {
45+
sanitizePolicy := bluemonday.UGCPolicy()
46+
body, _ := ioutil.ReadAll(p.Body)
47+
plainTextBody := strings.Replace(string(body), "\n", "\\n", -1)
48+
plainTextBody = strings.Replace(plainTextBody, "\r", "\\r", -1)
49+
return sanitizePolicy.Sanitize(plainTextBody)
50+
}
51+
52+
func makePostRequest(endpoint string, body string) *http.Response {
53+
jiraUrl := os.Getenv("JIRA_URL")
54+
jiraUser := os.Getenv("JIRA_USER")
55+
jiraPassword := os.Getenv("JIRA_PASSWORD")
56+
57+
clt := &http.Client{}
58+
req, err := http.NewRequest("POST", jiraUrl+endpoint, strings.NewReader(body))
59+
req.Header.Add("Content-Type", "application/json")
60+
req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(jiraUser+":"+jiraPassword)))
61+
resp, err := clt.Do(req)
62+
if err != nil {
63+
log.Fatal(err)
64+
}
65+
defer resp.Body.Close()
66+
67+
return resp
68+
}
69+
70+
func makeGetRequest(endpoint string) *http.Response {
71+
jiraUrl := os.Getenv("JIRA_URL")
72+
jiraUser := os.Getenv("JIRA_USER")
73+
jiraPassword := os.Getenv("JIRA_PASSWORD")
74+
75+
clt := &http.Client{}
76+
req, err := http.NewRequest("GET", jiraUrl+endpoint, strings.NewReader(""))
77+
req.Header.Add("Content-Type", "application/json")
78+
req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(jiraUser+":"+jiraPassword)))
79+
resp, err := clt.Do(req)
80+
if err != nil {
81+
log.Fatal(err)
82+
}
83+
defer resp.Body.Close()
84+
85+
return resp
86+
}
87+
88+
func setMailAsUnseen(c *client.Client, currentMail uint32) {
89+
seqSet := new(imap.SeqSet)
90+
seqSet.AddRange(currentMail, currentMail)
91+
92+
if err := c.Store(seqSet, imap.RemoveFlags, []interface{}{imap.SeenFlag}, nil); err != nil {
93+
log.Println("IMAP Message Flag Update Failed")
94+
log.Println(err)
95+
os.Exit(1)
96+
}
97+
98+
if err := c.Expunge(nil); err != nil {
99+
log.Println("IMAP Message mark as unseen Failed")
100+
os.Exit(1)
101+
}
102+
}
103+
34104
func run() {
35105

36106
// ============================================================
@@ -42,10 +112,6 @@ func run() {
42112
imapUser := os.Getenv("IMAP_USER")
43113
imapPassword := os.Getenv("IMAP_PASSWORD")
44114

45-
jiraUrl := os.Getenv("JIRA_URL")
46-
jiraUser := os.Getenv("JIRA_USER")
47-
jiraPassword := os.Getenv("JIRA_PASSWORD")
48-
49115
// Connect to server
50116
c, err := client.DialTLS(imapServer+":"+imapServerPort, nil)
51117
if err != nil {
@@ -63,7 +129,7 @@ func run() {
63129
log.Println("Logged in")
64130

65131
// Select INBOX
66-
mbox, err := c.Select("INBOX", false)
132+
_, err = c.Select("INBOX", false)
67133
if err != nil {
68134
log.Fatal(err)
69135
}
@@ -81,19 +147,34 @@ func run() {
81147

82148
var section imap.BodySectionName
83149
items := []imap.FetchItem{section.FetchItem(), imap.FetchEnvelope}
84-
sanitizePolicy := bluemonday.UGCPolicy()
85150

86-
messages := make(chan *imap.Message, 1)
87-
_ = c.Fetch(messageSet, items, messages)
151+
messages := make(chan *imap.Message, len(uids))
152+
err = c.Fetch(messageSet, items, messages)
153+
154+
currentMessage := -1
88155

89156
for message := range messages {
157+
currentMessage = currentMessage + 1
158+
currentUid := uids[currentMessage]
159+
90160
r := message.GetBody(&section)
161+
subject := message.Envelope.Subject
162+
163+
isMessageWithIssueNumber, _ := regexp.MatchString("^.*\\[.*-\\d+]$", subject)
91164

92165
mr, err := mail.CreateReader(r)
93166
if err != nil {
94167
log.Fatal(err)
95168
}
96169

170+
header := mr.Header
171+
senderArray, err := header.AddressList("From")
172+
if err != nil {
173+
log.Fatal(err)
174+
}
175+
176+
sender := senderArray[0]
177+
97178
for {
98179
p, err := mr.NextPart()
99180
if err == io.EOF {
@@ -104,61 +185,55 @@ func run() {
104185

105186
switch p.Header.(type) {
106187
case *mail.InlineHeader:
107-
// This is the message's text (can be plain-text or HTML)
108-
body, _ := ioutil.ReadAll(p.Body)
109-
plainTextBody := strip.StripTags(string(body))
110-
plainTextBody = strings.Replace(plainTextBody, "\n", "\\n", -1)
111-
plainTextBody = strings.Replace(plainTextBody, "\r", "\\r", -1)
112-
sanitizedBody := sanitizePolicy.Sanitize(plainTextBody)
113-
114-
content, err := ioutil.ReadFile("structure.json")
115-
if err != nil {
116-
log.Fatal(err)
117-
}
118-
119-
// Convert []byte to string and print to screen
120-
jsonString := string(content)
121-
jsonString = strings.Replace(jsonString, "%SUMMARY%", message.Envelope.Subject, 1)
122-
jsonString = strings.Replace(jsonString, "%DESCRIPTION%", strings.TrimSpace(sanitizedBody), 1)
123-
124-
log.Println(jsonString)
125-
126-
clt := &http.Client{}
127-
128-
req, err := http.NewRequest("POST", jiraUrl+"/rest/api/latest/issue", strings.NewReader(jsonString))
129-
req.Header.Add("Content-Type", "application/json")
130-
req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(jiraUser+":"+jiraPassword)))
131-
resp, err := clt.Do(req)
132-
if err != nil {
133-
log.Fatal(err)
134-
}
135-
defer resp.Body.Close()
188+
sanitizedBody := getMailBody(p)
136189

137-
if resp.StatusCode != 201 {
138-
bodyBytes, err := ioutil.ReadAll(resp.Body)
190+
if isMessageWithIssueNumber {
191+
content, err := ioutil.ReadFile("structure_add_comment.json")
139192
if err != nil {
140193
log.Fatal(err)
141194
}
142-
bodyString := string(bodyBytes)
143-
err = errors.New(bodyString)
144-
log.Print(err)
145-
} else {
146-
delSeqset := new(imap.SeqSet)
147-
delSeqset.AddRange(mbox.Messages, mbox.Messages)
148-
149-
flags := []interface{}{imap.SeenFlag}
150-
if err := c.Store(delSeqset, imap.FormatFlagsOp(imap.AddFlags, true), flags, nil); err != nil {
151-
log.Println("IMAP Message Flag Update Failed")
152-
log.Println(err)
153-
os.Exit(1)
195+
// Convert []byte to string and print to screen
196+
jsonString := string(content)
197+
jsonString = strings.Replace(jsonString, "%SUMMARY%", subject+" ("+sender.Name+" <"+sender.Address+">)", 1)
198+
jsonString = strings.Replace(jsonString, "%DESCRIPTION%", strings.TrimSpace(sanitizedBody), 1)
199+
200+
issueNumber := subject[strings.LastIndex(subject, "[")+1 : strings.LastIndex(subject, "]")]
201+
202+
resp := makeGetRequest("/rest/api/3/issue/" + issueNumber)
203+
204+
if resp.StatusCode != 200 {
205+
setMailAsUnseen(c, currentUid)
206+
printErrorFromApi(resp)
207+
} else {
208+
resp := makePostRequest("/rest/api/3/issue/"+issueNumber+"/comment", jsonString)
209+
210+
if resp.StatusCode != 201 {
211+
setMailAsUnseen(c, currentUid)
212+
printErrorFromApi(resp)
213+
} else {
214+
log.Println("Success add comment")
215+
}
154216
}
155217

156-
if err := c.Expunge(nil); err != nil {
157-
log.Println("IMAP Message mark as Seen Failed")
158-
os.Exit(1)
218+
} else {
219+
content, err := ioutil.ReadFile("structure_new_issue.json")
220+
if err != nil {
221+
log.Fatal(err)
159222
}
160223

161-
log.Println("Success")
224+
// Convert []byte to string and print to screen
225+
jsonString := string(content)
226+
jsonString = strings.Replace(jsonString, "%SUMMARY%", subject, 1)
227+
jsonString = strings.Replace(jsonString, "%DESCRIPTION%", strings.TrimSpace(sanitizedBody), 1)
228+
229+
resp := makePostRequest("/rest/api/3/issue", jsonString)
230+
231+
if resp.StatusCode != 201 {
232+
setMailAsUnseen(c, currentUid)
233+
printErrorFromApi(resp)
234+
} else {
235+
log.Println("Success add issue")
236+
}
162237
}
163238
}
164239
break

structure.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

structure_add_comment.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"body":{"type":"doc","version":1,"content":[{"type":"paragraph","content":[{"text":"%SUMMARY%","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"%DESCRIPTION%","type":"text"}]}]}}

structure_new_issue.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"fields":{"project":{"key":"TES"},"summary":"%SUMMARY%","description":{"type":"doc","version":1,"content":[{"type":"paragraph","content":[{"text":"%DESCRIPTION%","type":"text"}]}]},"issuetype":{"name":"Task"}}}

0 commit comments

Comments
 (0)