From c7c0dc92d7088f0d4470e28e6aaa77e7dcd8b1c8 Mon Sep 17 00:00:00 2001 From: Daniel O'Connell Date: Mon, 7 Oct 2019 22:28:07 +0200 Subject: [PATCH] Get worklogs from emails --- project.clj | 2 + src/invoices/core.clj | 18 +----- src/invoices/email.clj | 74 +++++++++++++++++++++++ src/invoices/time.clj | 6 +- src/invoices/{jira.clj => timesheets.clj} | 2 +- 5 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 src/invoices/email.clj rename src/invoices/{jira.clj => timesheets.clj} (97%) diff --git a/project.clj b/project.clj index 1e3323d..4735784 100644 --- a/project.clj +++ b/project.clj @@ -6,6 +6,8 @@ :dependencies [[org.clojure/clojure "1.10.0"] [org.clojure/tools.cli "0.4.2"] [com.draines/postal "2.0.3"] + [io.forward/clojure-mail "1.0.8"] + [commons-net "3.6"] [clj-http "3.10.0"] [cheshire "5.9.0"] [clj-pdf "2.4.0"]] diff --git a/src/invoices/core.clj b/src/invoices/core.clj index ed5a5aa..bacde2b 100644 --- a/src/invoices/core.clj +++ b/src/invoices/core.clj @@ -1,30 +1,18 @@ (ns invoices.core (:require [invoices.pdf :as pdf] [invoices.settings :refer [invoices]] - [invoices.jira :refer [prev-timesheet]] + [invoices.timesheets :refer [prev-timesheet]] [invoices.time :refer [prev-month last-working-day date-applies?]] [invoices.calc :refer [set-price]] + [invoices.email :refer [send-email]] [clojure.tools.cli :refer [parse-opts]] [clojure.string :as str] - [clojure.java.shell :refer [sh]] - [postal.core :refer [send-message]]) + [clojure.java.shell :refer [sh]]) (:gen-class)) (defn invoice-number [when number] (->> [(or number 1) (-> when .getMonthValue) (-> when .getYear)] (map str) (str/join "/"))) -(defn send-email [to from {smtp :smtp} invoice] - (when (not-any? nil? [to from smtp invoice]) - (->> - (send-message smtp {:from from - :to [to] - :subject invoice - :body [{:type :attachment - :content (java.io.File. (str invoice ".pdf")) - :content-type "application/pdf"}]}) - :error (= :SUCCESS) - (println " - email sent: ")))) - (defn run-callback [file callback] (let [command (concat callback [file]) str-command (str/join " " command) diff --git a/src/invoices/email.clj b/src/invoices/email.clj new file mode 100644 index 0000000..99f71a9 --- /dev/null +++ b/src/invoices/email.clj @@ -0,0 +1,74 @@ +(ns invoices.email + (:require [clojure.string :as str] + [invoices.time :as time] + [postal.core :refer [send-message]] + [clojure-mail.core :as mail] + [clojure-mail.gmail :as gmail] + [clojure-mail.folder :as folder] + [clojure-mail.message :refer (read-message) :as mess])) + +(defn send-email [to from {smtp :smtp} invoice] + (when (not-any? nil? [to from smtp invoice]) + (->> + (send-message smtp {:from from + :to [to] + :subject invoice + :body [{:type :attachment + :content (java.io.File. (str invoice ".pdf")) + :content-type "application/pdf"}]}) + :error (= :SUCCESS) + (println " - email sent: ")))) + + +(defn server-find-messages + "Find all messages in the given folder, filtering them by subject and sender (use nil to ignore)." + [client folder subject from] + (->> + {:subject subject :from from} + (remove (comp nil? second)) + flatten + (apply folder/search (mail/open-folder client folder :readonly)) + (into []))) + +(defn manual-find-messages + "Find all messages in the given folder, filtering them by subject and sender (use nil to ignore). + + WARNING: This can be very slow, as it fetches each message seperately. + " + [client folder subject from] + (let [subjecter (if subject #(str/includes? (mess/subject %) subject) identity) + fromer (if from #(str/includes? (-> % mess/from first :address) from) identity)] + (filter + #(and (subjecter %) (fromer %)) + (mail/all-messages client folder)))) + +(defn find-messages + "Get all messages for the given month from the given imap server." + [month + {imap :host user :user password :pass try-manual :try-manual folder :folder from :from subject :subject}] + (let [client (mail/store "imaps" imap user password) + subject (time/format-month subject month) + messages (server-find-messages client folder subject from)] + (if (and (empty? messages) try-manual) + (manual-find-messages client folder subject from) + messages))) + +(defn split-cells [content] + (->> content + str/split-lines + (remove str/blank?) + (map #(str/split % #"[\s;]")))) + +(defn zip-item [headers cell] + (into (sorted-map) (map vector headers cell))) + +(defn extract-items [headers message] + (->> message + mess/get-content + split-cells + (map (partial zip-item headers)))) + +(defn get-worklogs + "Get all worklogs for the given month from the given imap server." + [month imap] + (->> imap (find-messages month) (map (partial extract-items (:headers imap))) flatten)) diff --git a/src/invoices/time.clj b/src/invoices/time.clj index ad3e61b..82b0c1e 100644 --- a/src/invoices/time.clj +++ b/src/invoices/time.clj @@ -1,4 +1,5 @@ -(ns invoices.time) +(ns invoices.time + (:require [clojure.string :as str])) (defn last-day @@ -25,3 +26,6 @@ [date {to :to from :from}] (and (or (nil? to) (-> date .toString (compare to) (< 0))) (or (nil? from) (-> date .toString (compare from) (>= 0))))) + +(defn format-month [formatter month] + (->> formatter (java.time.format.DateTimeFormatter/ofPattern) (.format month))) diff --git a/src/invoices/jira.clj b/src/invoices/timesheets.clj similarity index 97% rename from src/invoices/jira.clj rename to src/invoices/timesheets.clj index 2d0d723..5cf9618 100644 --- a/src/invoices/jira.clj +++ b/src/invoices/timesheets.clj @@ -1,4 +1,4 @@ -(ns invoices.jira +(ns invoices.timesheets (:require [clj-http.client :as client] [invoices.time :refer [last-day prev-month]]))