Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

mell!

  • Loading branch information...
commit a41b5a4bb66edb6c833963ffa14c4eb51cc2ad98 0 parents
@apage43 authored
11 .gitignore
@@ -0,0 +1,11 @@
+/target
+/lib
+/classes
+/checkouts
+pom.xml
+message-tmp.md
+*.jar
+*.class
+.lein-deps-sum
+.lein-failures
+.lein-plugins
20 README.md
@@ -0,0 +1,20 @@
+# mell
+## stupid markdown/javamail tricks
+
+Create a ~/.mellrc
+
+This is almost exactly the properties object passed to javamail's
+Session.getInstance, except can include a password and is a clojure map.
+
+See `mellrc.sample` for an example.
+
+ # type a mail in markdown using $EDITOR and send it
+ # the markdown will be sent as the text/plain part
+ $ lein run compose recipient@place.com
+ # type a mail in markdown using $EDITOR and save it to [Gmail]/Drafts IMAP folder
+ $ lein run draft recipient@place.com
+
+### dependencies that aren't magic
+
+ * pygments
+ * kramdown
10 mellrc.sample
@@ -0,0 +1,10 @@
+;; -*- mode: clojure; -*-
+; vim: ft=clojure
+{:mail {:store {:protocol "imaps"}
+ :transport {:protocol "smtps"}
+ :smtp {:host "smtp.gmail.com"
+ :auth "true"
+ :starttls.enable "true"}
+ :host "imap.gmail.com"
+ :user "me@gmail.com"
+ :password "password"}}
13 project.clj
@@ -0,0 +1,13 @@
+(defproject mell "0.1.0-SNAPSHOT"
+ :description "javamail stuff"
+ :url "http://github.com/apage43/mell"
+ :license {:name "WTFPL"
+ :url "http://sam.zoy.org/wtfpl/"}
+ :dependencies [[org.clojure/clojure "1.4.0"]
+ [clojure-lanterna "0.9.2"]
+ [crouton "0.1.1"]
+ [environ "0.3.0"]
+ [me.raynes/conch "0.4.0"]
+ [hiccup "1.0.1"]
+ [javax.mail/mail "1.4.5"] ]
+ :main mell.core)
140 src/mell/compose.clj
@@ -0,0 +1,140 @@
+(ns mell.compose
+ (:require [conch.sh :refer [with-programs let-programs]]
+ [environ.core :refer [env]]
+ [clojure.string :as string])
+ (:import [javax.mail.internet MimeMultipart
+ MimeMessage MimeBodyPart InternetHeaders
+ InternetAddress]
+ [javax.mail Transport]
+ [java.io ByteArrayOutputStream]
+ [org.jsoup Jsoup]))
+
+(def editor (env :editor "nano"))
+
+(defn mimeheaders [headermap]
+ (let [hobj (InternetHeaders.)]
+ (doseq [[k v] headermap] (.addHeader hobj k v))
+ hobj))
+
+(defn bodypart [headers ^bytes content]
+ (MimeBodyPart. (mimeheaders headers) content))
+
+(defn str->bodypart [headers content]
+ (bodypart headers (.getBytes content)))
+
+(defn markdown [istr]
+ (with-programs [kramdown]
+ (kramdown {:in istr})))
+
+(defn highlight [istr lex]
+ (with-programs [pygmentize]
+ (pygmentize "-l" lex "-f" "html" "-O" "cssclass=src" {:in istr})))
+
+(defn check-lang [code]
+ (let [clines (string/split-lines code)]
+ (when (= \` (ffirst clines))
+ [(string/join "\n" (rest clines))
+ (apply str (rest (first clines)))])))
+
+(defn hl-filter "highlight. returns a jsoup!" [htmlstr]
+ (let [soup (Jsoup/parse htmlstr)]
+ (doseq [p (.select soup "pre")]
+ (let [code (.text p)
+ checked (check-lang code)]
+ (when checked
+ (.after p (apply highlight checked))
+ (.remove p))))
+ soup))
+
+(def codestyles
+ {
+ ".hll" "background-color: #ffffcc"
+ ".c" "color: #999988; font-style: italic"
+ ".err" "color: #a61717; background-color: #e3d2d2"
+ ".k" "color: #000000; font-weight: bold"
+ ".o" "color: #000000; font-weight: bold"
+ ".cm" "color: #999988; font-style: italic"
+ ".cp" "color: #999999; font-weight: bold; font-style: italic"
+ ".c1" "color: #999988; font-style: italic"
+ ".cs" "color: #999999; font-weight: bold; font-style: italic"
+ ".gd" "color: #000000; background-color: #ffdddd"
+ ".ge" "color: #000000; font-style: italic"
+ ".gr" "color: #aa0000"
+ ".gh" "color: #999999"
+ ".gi" "color: #000000; background-color: #ddffdd"
+ ".go" "color: #888888"
+ ".gp" "color: #555555"
+ ".gs" "font-weight: bold"
+ ".gu" "color: #aaaaaa"
+ ".gt" "color: #aa0000"
+ ".kc" "color: #000000; font-weight: bold"
+ ".kd" "color: #000000; font-weight: bold"
+ ".kn" "color: #000000; font-weight: bold"
+ ".kp" "color: #000000; font-weight: bold"
+ ".kr" "color: #000000; font-weight: bold"
+ ".kt" "color: #445588; font-weight: bold"
+ ".m" "color: #009999"
+ ".s" "color: #d01040"
+ ".na" "color: #008080"
+ ".nb" "color: #0086B3"
+ ".nc" "color: #445588; font-weight: bold"
+ ".no" "color: #008080"
+ ".nd" "color: #3c5d5d; font-weight: bold"
+ ".ni" "color: #800080"
+ ".ne" "color: #990000; font-weight: bold"
+ ".nf" "color: #990000; font-weight: bold"
+ ".nl" "color: #990000; font-weight: bold"
+ ".nn" "color: #555555"
+ ".nt" "color: #000080"
+ ".nv" "color: #008080"
+ ".ow" "color: #000000; font-weight: bold"
+ ".w" "color: #bbbbbb"
+ ".mf" "color: #009999"
+ ".mh" "color: #009999"
+ ".mi" "color: #009999"
+ ".mo" "color: #009999"
+ ".sb" "color: #d01040"
+ ".sc" "color: #d01040"
+ ".sd" "color: #d01040"
+ ".s2" "color: #d01040"
+ ".se" "color: #d01040"
+ ".sh" "color: #d01040"
+ ".si" "color: #d01040"
+ ".sx" "color: #d01040"
+ ".sr" "color: #009926"
+ ".s1" "color: #d01040"
+ ".ss" "color: #990073"
+ ".bp" "color: #999999"
+ ".vc" "color: #008080"
+ ".vg" "color: #008080"
+ ".vi" "color: #008080"
+ ".il" "color: #009999"
+ })
+
+(defn style-filter
+ [soup]
+ (doseq [[cls style] codestyles]
+ (doseq [el (.select soup cls)]
+ (.attr el "style" style)))
+ soup)
+
+(defn markup [body]
+ (-> body markdown hl-filter style-filter str))
+
+(defn compose [session addrs]
+ (with-programs [sh]
+ (spit "message-tmp.md" (str "To: " (string/join ", " addrs)
+ "\r\nSubject: \r\n\r\n"))
+ (sh "-c" (str editor " message-tmp.md"))
+ (let [message (slurp "message-tmp.md")
+ [headers-raw body] (string/split message #"\r?\n\r?\n" 2)
+ headers (into {} (map #(mapv string/trim (string/split % #":" 2)) (string/split-lines headers-raw)))
+ textpart (str->bodypart {"Content-Type" "text/plain"} body)
+ htmlpart (str->bodypart {"Content-Type" "text/html"} (markup body))
+ multipart (doto (MimeMultipart. "alternative")
+ (.addBodyPart textpart)
+ (.addBodyPart htmlpart))
+ mimemessage (MimeMessage. session)]
+ (.setContent mimemessage multipart)
+ (doseq [[k v] headers] (.addHeader mimemessage k v))
+ mimemessage)))
25 src/mell/core.clj
@@ -0,0 +1,25 @@
+(ns mell.core
+ (:use clojure.pprint
+ mell.util)
+ (:require [clojure.string :as s]
+ [mell.compose :refer [compose]]
+ [mell.mail :as mail])
+ (:gen-class))
+
+(defn idp [t] (pr t (keys (bean t))) t)
+
+(defn load-config []
+ (let [mtext (or (slurp (str (get (System/getenv) "HOME") "/.mellrc")) "{}")]
+ (read-string mtext)))
+
+(defn -main
+ [cmd & args]
+ (let [config (load-config)
+ session (mail/session config)
+ store (mail/connect session)]
+ (case cmd
+ "compose" (mail/send-msg (compose session args))
+ "draft" (mail/insert (mail/folder (mail/connect session)
+ "[Gmail]/Drafts")
+ (compose session args)))
+ (System/exit 0)))
42 src/mell/mail.clj
@@ -0,0 +1,42 @@
+(ns mell.mail
+ (:require [mell.util :as mu])
+ (:import [javax.mail Session Store Folder Message Transport]
+ [javax.mail.internet MimeMultipart]))
+
+(defn authenticator [conf]
+ (proxy [javax.mail.Authenticator] []
+ (getPasswordAuthentication []
+ (javax.mail.PasswordAuthentication.
+ (:user (:mail conf))
+ (:password (:mail conf))))))
+
+(defn session [conf]
+ (Session/getInstance (mu/to-props conf) (authenticator conf)))
+
+(defn connect [sess]
+ (doto (.getStore sess)
+ (.connect nil (get (.getProperties sess) "mail.password"))))
+
+(defn folder
+ ([conn fl]
+ (folder conn fl Folder/READ_ONLY))
+ ([conn fl access]
+ (doto (.getFolder conn fl) (.open access))))
+
+(defn only-mime [parts mimetype]
+ (filter #(.isMimeType % mimetype) parts))
+
+(defn parts [multipart]
+ (map #(.getBodyPart multipart %) (range (.getCount multipart))))
+
+(defn text-part [content]
+ (if (instance? MimeMultipart content)
+ (let [plainpart (first (only-mime (parts content) "text/plain"))]
+ (if plainpart (.getContent plainpart) ""))
+ content))
+
+(defn send-msg [message]
+ (Transport/send message))
+
+(defn insert [folder message]
+ (.addMessages folder (into-array Message [message])))
17 src/mell/util.clj
@@ -0,0 +1,17 @@
+(ns mell.util
+ (:require clojure.walk))
+
+(defn- dotify-collapse-1 [m]
+ (if (map? m)
+ (into {} (mapcat (fn [[ok ov]]
+ (if (map? ov)
+ (map (fn [[ik iv]] [(str (name ok) "." (name ik)) iv]) ov)
+ [[ok ov]])) m))m))
+
+(defn dotify-collapse [m]
+ (clojure.walk/postwalk dotify-collapse-1 m))
+
+(defn to-props [m]
+ (doto (java.util.Properties.)
+ (.putAll (dotify-collapse m))))
+
Please sign in to comment.
Something went wrong with that request. Please try again.