Skip to content

Instantly share code, notes, and snippets.

@jantobola
Created February 1, 2020 14:02
Show Gist options
  • Save jantobola/c2edc62f7cdb04bfc0245ccc793a4133 to your computer and use it in GitHub Desktop.
Save jantobola/c2edc62f7cdb04bfc0245ccc793a4133 to your computer and use it in GitHub Desktop.
@file:DependsOn("com.github.doyaaaaaken:kotlin-csv-jvm:0.7.3")
@file:DependsOn("com.sun.mail:javax.mail:1.6.2")
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
import java.io.FileNotFoundException
import java.lang.Exception
import java.net.PasswordAuthentication
import java.nio.charset.StandardCharsets
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import javax.mail.Authenticator
import javax.mail.Message
import javax.mail.Session
import javax.mail.Transport
import javax.mail.internet.InternetAddress
import javax.mail.internet.MimeMessage
fun main(args: Array<String>) {
if (args.size != 3) {
throw IllegalArgumentException("""
1st arg must be a folder path with html templates.
2nd arg must be template filename pattern.
3rd arg must be source list.
""".trimIndent())
}
val templateDir = Paths.get(args[0])
val templateFilePattern = args[1]
val datasource = Paths.get(args[2])
if (!templateDir.toFile().exists()) {
throw FileNotFoundException("Template folder does not exist.")
}
if (!datasource.toFile().exists()) {
throw FileNotFoundException("Datasource file does not exist.")
}
println("""
templates: ${templateDir.toAbsolutePath()}/$templateFilePattern
datasource: ${datasource.toAbsolutePath()}
""".trimIndent())
sendEmails(prepareDatasource(datasource, templateDir, templateFilePattern)) { ds ->
val gmailSessions = ds.asSequence().map { it.getValue("from") }.distinct().map {
Pair(it, prepareGmailSession(it, readPassword(it)))
}.toMap()
ds.forEach { data ->
println("Sending email to ${data["name"]} ${data["surname"]} (${data["to"]}) (from: ${data["from"]})")
val template = data["template"] ?: error("template unknown")
val templateLines = Paths.get(template).toFile().readLines()
val emailBody = templateLines.map { line ->
var replacingLine = line
data["greeting"]?.let { replacingLine = replacingLine.replace("\${greeting}", it) }
data["greeting_name"]?.let { replacingLine = replacingLine.replace("\${greeting_name}", it) }
data["enjoy"]?.let { replacingLine = replacingLine.replace("\${enjoy}", it) }
data["have"]?.let { replacingLine = replacingLine.replace("\${have}", it) }
data["can"]?.let { replacingLine = replacingLine.replace("\${can}", it) }
replacingLine
}.joinToString("\n")
val from = data.getValue("from")
val senderName = data.getValue("from_name")
val to = data.getValue("to")
val subject = data.getValue("subject")
val message = prepareEmailMessage(gmailSessions.getValue(from), from, senderName, to, subject, emailBody)
try {
message.send()
} catch (ex: Exception) {
System.err.println("Cannot send email (${ex.message})")
}
}
}
}
fun sendEmails(datasource: List<Map<String, String>>, func: (datasource: List<Map<String, String>>) -> Unit) {
println("Do you really want to send ${datasource.size} emails?")
confirmLoop@ while (true) {
when (readLine()) {
"yes", "y" -> {
func(datasource)
break@confirmLoop
}
"no", "n" -> break@confirmLoop
else -> println("Invalid input - answer yes/no")
}
}
}
class EmailMessage(val session: SessionWrapper, val message: MimeMessage) {
fun send() {
Transport.send(message, session.username, session.password)
}
}
fun prepareEmailMessage(session: SessionWrapper, from: String, senderName: String, to: String, subject: String, body: String) : EmailMessage {
val message = MimeMessage(session.session)
message.setFrom(InternetAddress(from, senderName))
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to))
message.setSubject(subject, StandardCharsets.UTF_8.toString())
message.setContent(body, "text/html;charset=UTF-8")
return EmailMessage(session, message)
}
data class SessionWrapper(val session: Session, val username: String, val password: String)
fun prepareGmailSession(username: String, password: String) : SessionWrapper {
val prop = Properties()
prop["mail.smtp.host"] = "smtp.gmail.com";
prop["mail.smtp.port"] = "465";
prop["mail.smtp.auth"] = "true";
prop["mail.smtp.socketFactory.port"] = "465";
prop["mail.smtp.socketFactory.class"] = "javax.net.ssl.SSLSocketFactory"
val session: Session = Session.getInstance(prop,
object : Authenticator() {
protected val passwordAuthentication: PasswordAuthentication?
get() = PasswordAuthentication(username, password.toCharArray())
})
return SessionWrapper(session, username, password)
}
fun prepareDatasource(datasource: Path, templateDir: Path, templateNamePattern: String) : List<Map<String, String>> {
println("Parsing datasource...")
val datasourceContent = csvReader().readAllWithHeader(datasource.toFile())
val datasourceContentFiltered = datasourceContent.filter { !it["to"].isNullOrBlank() }.toList()
println("Found ${datasourceContentFiltered.size} recipients")
return datasourceContentFiltered.map {
val map = it.toMutableMap()
val template = Paths.get(
templateDir.toAbsolutePath().toString(),
templateNamePattern.replace("\${template_nr}", map["template_nr"]!!)
)
if (!template.toFile().exists()) {
throw FileNotFoundException("Template file '$template' does not exist.")
}
map["template"] = template.toString()
map
}.toList()
}
fun readPassword(account: String): String {
return readNonNull(
message = "Enter password for account '$account':"
)
}
fun readNonNull(
message: String = "Enter value:",
successPredicate: (String) -> Boolean = { it.isNotBlank() },
failMessage: String = "Input must not be blank",
doWithAnswer: (String) -> String = { it }
): String {
while (true) {
println(message)
val answer: String = readLine() ?: ""
if (successPredicate(answer)) {
println()
return doWithAnswer(answer)
} else {
println(failMessage)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment