Instantly share code, notes, and snippets.
Created
February 1, 2020 14:02
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save jantobola/c2edc62f7cdb04bfc0245ccc793a4133 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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