2022-10-22 21:36:37 +02:00
|
|
|
package io.gitlab.jfronny.gitea.helpdesk;
|
|
|
|
|
|
|
|
import io.gitlab.jfronny.commons.StringFormatter;
|
|
|
|
import io.gitlab.jfronny.commons.throwable.ThrowingBiConsumer;
|
|
|
|
import io.gitlab.jfronny.gitea.helpdesk.db.DBInterface;
|
|
|
|
import io.gitlab.jfronny.gitea.helpdesk.db.Subscription;
|
|
|
|
import io.gitlab.jfronny.gitea.helpdesk.gitea.*;
|
2022-10-22 23:29:07 +02:00
|
|
|
import io.gitlab.jfronny.gitea.helpdesk.mail.*;
|
2022-10-22 21:36:37 +02:00
|
|
|
import io.gitlab.jfronny.gitea.helpdesk.web.WebInterface;
|
|
|
|
import jakarta.mail.MessagingException;
|
2022-10-22 23:55:55 +02:00
|
|
|
import org.commonmark.parser.Parser;
|
|
|
|
import org.commonmark.renderer.html.HtmlRenderer;
|
2022-10-22 21:36:37 +02:00
|
|
|
|
|
|
|
import java.io.FileNotFoundException;
|
|
|
|
import java.io.IOException;
|
2023-01-26 20:32:24 +01:00
|
|
|
import java.util.List;
|
2022-10-22 21:36:37 +02:00
|
|
|
|
2023-01-26 20:32:24 +01:00
|
|
|
public record UpdateTask(DBInterface db, MailInterface mail, GiteaInterface gitea, WebInterface web, List<String> ignored) implements Runnable {
|
2022-10-22 23:55:55 +02:00
|
|
|
private static final Parser MARKDOWN_PARSER = Parser.builder().build();
|
|
|
|
private static final HtmlRenderer MARKDOWN_RENDER = HtmlRenderer.builder().build();
|
2022-10-22 23:29:07 +02:00
|
|
|
private static final String TEMPLATE = Main.getResource("/mail/template.html");
|
|
|
|
private static final String MAIL_ERROR = Main.getResource("/mail/error.html");
|
|
|
|
private static final String MAIL_UNEXPECTED = Main.getResource("/mail/unexpected.html");
|
|
|
|
private static final String MAIL_CREATE = Main.getResource("/mail/create.html");
|
|
|
|
private static final String MAIL_COMMENT = Main.getResource("/mail/comment.html");
|
|
|
|
private static final String MAIL_COMMENT_CLOSED = Main.getResource("/mail/comment_closed.html");
|
|
|
|
private static final String MAIL_ISSUE_DELETED = Main.getResource("/mail/issue_deleted.html");
|
|
|
|
private static final String MAIL_ISSUE_CLOSED = Main.getResource("/mail/issue_closed.html");
|
2022-10-22 21:36:37 +02:00
|
|
|
|
2022-10-22 23:29:07 +02:00
|
|
|
private static String template(String body) {
|
|
|
|
return TEMPLATE.formatted(WebInterface.THEME, body);
|
2022-10-22 21:36:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
try {
|
|
|
|
updateSubscriptions();
|
|
|
|
processEmails();
|
2022-10-22 23:29:07 +02:00
|
|
|
} catch (Throwable e) {
|
2022-10-22 21:36:37 +02:00
|
|
|
Main.LOG.error("Could not run update task", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void updateSubscriptions() throws Exception {
|
|
|
|
String[] addressParts = mail.getAddress().split("@");
|
|
|
|
db.forEachSubscription(subscription -> {
|
|
|
|
ThrowingBiConsumer<String, String, Exception> reply = (content, subject) -> {
|
|
|
|
String[] previousMessages = subscription.referenceChain().split(" ");
|
|
|
|
String previousMessageId = previousMessages[previousMessages.length - 1];
|
|
|
|
mail.reply(addressParts[0] + "+reply+" + subscription.id(), subscription.email(), content, subject, previousMessageId, subscription.referenceChain(), null);
|
|
|
|
};
|
|
|
|
GiteaIssue issue;
|
|
|
|
try {
|
|
|
|
issue = gitea.getIssue(subscription.repoOwner(), subscription.repo(), subscription.issue());
|
|
|
|
} catch (FileNotFoundException fe) {
|
2022-10-22 23:29:07 +02:00
|
|
|
reply.accept(template(MAIL_ISSUE_DELETED), "Issue deleted");
|
2022-10-22 21:36:37 +02:00
|
|
|
db.removeSubscription(subscription.id());
|
|
|
|
return;
|
|
|
|
}
|
2022-10-22 23:55:55 +02:00
|
|
|
if (issue.state.equals("closed")) {
|
2022-10-22 23:29:07 +02:00
|
|
|
reply.accept(template(MAIL_ISSUE_CLOSED), "Issue closed");
|
2022-10-22 21:36:37 +02:00
|
|
|
db.removeSubscription(subscription.id());
|
|
|
|
}
|
|
|
|
for (GiteaIssueComment comment : gitea.getComments(subscription.repoOwner(), subscription.repo(), subscription.issue())) {
|
|
|
|
if (comment.id > subscription.issueComment()) {
|
2022-10-22 23:55:55 +02:00
|
|
|
reply.accept(MARKDOWN_RENDER.render(MARKDOWN_PARSER.parse(comment.body)), issue.title);
|
2022-10-22 21:36:37 +02:00
|
|
|
db.updateSubscriptionIssueComment(subscription.id(), comment.id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-10-22 23:29:07 +02:00
|
|
|
private void processEmails() throws MessagingException {
|
2022-10-22 21:36:37 +02:00
|
|
|
String[] addressParts = mail.getAddress().split("@");
|
2022-10-22 23:29:07 +02:00
|
|
|
try (WrappedMessageSet wms = mail.getInbox()) {
|
|
|
|
for (WrappedMessage message : wms) {
|
|
|
|
try {
|
2023-01-26 20:32:24 +01:00
|
|
|
if (ignored.contains(message.getSender())) {
|
|
|
|
message.delete();
|
|
|
|
continue;
|
|
|
|
}
|
2022-10-22 23:29:07 +02:00
|
|
|
String[] args = message.getRecipientSubAddress().split("\\+");
|
|
|
|
switch (args[0]) {
|
|
|
|
case "create" -> {
|
|
|
|
if (args.length != 3) throw new UnexpectedMailException("Create classifier only allows two parameters");
|
|
|
|
String owner = args[1];
|
|
|
|
String repo = args[2];
|
|
|
|
checkArgs(owner, repo);
|
|
|
|
GiteaIssue issue = gitea.createIssue(owner, repo, message.getSubject(), formatBody(message));
|
2022-10-22 23:55:55 +02:00
|
|
|
String id = db.addSubscription(message.getSender(), owner, repo, issue.id, 0, message.getMessageID(), true);
|
|
|
|
String unsubscribeUrl = web.getAddress() + "/unsubscribe?id=" + id;
|
|
|
|
String closeUrl = web.getAddress() + "/close?id=" + id;
|
|
|
|
message.reply(addressParts[0] + "+reply+" + id + '@' + addressParts[1], template(MAIL_CREATE.formatted(issue.url, unsubscribeUrl, closeUrl)));
|
2022-10-22 23:29:07 +02:00
|
|
|
}
|
|
|
|
case "reply" -> {
|
|
|
|
if (args.length != 2) throw new UnexpectedMailException("Reply classifier only allows one parameter");
|
|
|
|
Subscription sub = db.getSubscription(args[1]).orElseThrow(() -> new UnexpectedMailException("Reply classifier does not represent an active issue"));
|
|
|
|
GiteaIssueComment commentId = gitea.addComment(sub.repoOwner(), sub.repo(), sub.issue(), formatBody(message));
|
|
|
|
db.updateSubscriptionIssueComment(sub.id(), commentId.id);
|
2022-10-22 23:55:55 +02:00
|
|
|
db.updateSubscriptionReferenceChain(sub.id(), sub.referenceChain() + " " + message.getMessageID());
|
2022-10-22 23:29:07 +02:00
|
|
|
}
|
|
|
|
case "comment" -> {
|
2022-10-22 23:55:55 +02:00
|
|
|
if (args.length != 4) throw new UnexpectedMailException("Comment classifier requires four parameters");
|
2022-10-22 23:29:07 +02:00
|
|
|
String owner = args[1];
|
|
|
|
String repo = args[2];
|
|
|
|
long issueId = Long.parseLong(args[3]);
|
|
|
|
checkArgs(owner, repo);
|
|
|
|
GiteaIssue issue;
|
|
|
|
try {
|
|
|
|
issue = gitea.getIssue(owner, repo, issueId);
|
|
|
|
} catch (FileNotFoundException fe) {
|
|
|
|
throw new UnexpectedMailException("This issue does not exist");
|
|
|
|
}
|
2022-10-22 23:55:55 +02:00
|
|
|
long issueComment = gitea.addComment(owner, repo, issueId, formatBody(message)).id;
|
2022-10-22 23:29:07 +02:00
|
|
|
if (issue.state.equals("closed")) {
|
2022-10-22 23:55:55 +02:00
|
|
|
message.reply(mail.getAddress(), template(MAIL_COMMENT_CLOSED.formatted(issue.url)));
|
2022-10-22 23:29:07 +02:00
|
|
|
} else {
|
2022-10-22 23:55:55 +02:00
|
|
|
String id = db.addSubscription(message.getSender(), owner, repo, issue.id, issueComment, message.getMessageID(), false);
|
|
|
|
String unsubscribeUrl = web.getAddress() + "/unsubscribe?id=" + id;
|
|
|
|
message.reply(addressParts[0] + "+reply+" + id + '@' + addressParts[1], template(MAIL_COMMENT.formatted(issue.url, unsubscribeUrl)));
|
2022-10-22 23:29:07 +02:00
|
|
|
}
|
2022-10-22 21:36:37 +02:00
|
|
|
}
|
2022-10-22 23:29:07 +02:00
|
|
|
default -> throw new UnexpectedMailException("Did not expect classifier " + args[0]);
|
2022-10-22 21:36:37 +02:00
|
|
|
}
|
2022-10-22 23:29:07 +02:00
|
|
|
} catch (UnexpectedMailException | NumberFormatException t) {
|
|
|
|
message.reply(mail.getAddress(), template(MAIL_UNEXPECTED.formatted(WebInterface.escapeHTML(StringFormatter.toString(t)))));
|
|
|
|
} catch (Throwable t) {
|
|
|
|
Main.LOG.error("Could not parse mail", t);
|
|
|
|
message.reply(mail.getAddress(), template(MAIL_ERROR.formatted(WebInterface.escapeHTML(StringFormatter.toString(t)))));
|
|
|
|
} finally {
|
|
|
|
message.delete();
|
2022-10-22 21:36:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void checkArgs(String owner, String repo) throws UnexpectedMailException {
|
2022-10-22 23:29:07 +02:00
|
|
|
if (!Main.PATH_SEGMENT_PATTERN.matcher(owner).matches()) throw new UnexpectedMailException("Unsupported owner string");
|
|
|
|
if (!Main.PATH_SEGMENT_PATTERN.matcher(repo).matches()) throw new UnexpectedMailException("Unsupported repo string");
|
2022-10-22 21:36:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private String formatBody(WrappedMessage message) throws UnexpectedMailException, MessagingException, IOException {
|
|
|
|
return "Submitted by " + message.getSenderName() + ":\n\n" + message.getText();
|
|
|
|
}
|
|
|
|
}
|