アプリ開発備忘録

PlayStationMobile、Android、UWPの開発備忘録

Jakarta Mailでメールを確認する【Kotlin】

Jakarta Mail(旧 Java Mail)でメールを取得します。

build.gradle

implementation("jakarta.mail:jakarta.mail-api:2.1.2")
implementation("org.eclipse.angus:jakarta.mail:2.0.2")

imports

import java.net.URLEncoder
import jakarta.mail.Authenticator
import jakarta.mail.BodyPart
import jakarta.mail.Folder
import jakarta.mail.PasswordAuthentication
import jakarta.mail.Session
import jakarta.mail.internet.MimeMultipart
import org.eclipse.angus.mail.imap.IMAPMessage
import java.util.*
import org.eclipse.angus.mail.util.BASE64DecoderStream

コード説明

Sessionを作成します。IMAP4を使用しています。

private val session = Session.getDefaultInstance(
    Properties().also {
        it.setProperty("mail.imap.ssl.enable", "true");
        it.setProperty("mail.imap.host", "mail.host");
        it.setProperty("mail.imap.port", "993");
    },
    object : Authenticator() {
        override fun getPasswordAuthentication(): PasswordAuthentication {
            return PasswordAuthentication(
                "user name",
                "password",
            )
        }
    },
)

messageを取得します。Stringが入っている場合もあれば、HTMLや画像データが入っている事もあります。パース処理は後述。

fun main(args: Array<String>) {
    session.getStore("imap").also {
        it.connect()
    }.use { imap4 ->
        imap4.getFolder("INBOX").also {
            it.open(Folder.READ_ONLY)
        }.use { folder ->
            folder.messages
                .map { it as IMAPMessage }
                .mapIndexed { index, it ->
                    println("======================message index=$index=========================")
                    println("subject: ${it.subject}")
                    when (val content = it.dataHandler.content) {
                        is String -> {
                            println("String: ${content.toString().toPreviewString()}")
                        }

                        is MimeMultipart -> {
                            MultipartParser.parseMultipart(content, 0)
                        }

                        else -> {
                            println("=============Unknown=============")
                            println(it.dataHandler.content)
                        }
                    }
                }
        }
    }
}

コンソールに出力すると意図しない表示になるので、encodeして、先頭だけ見ます。MimeMultipartがネストしている事もあります。

private fun String.toPreviewString() : String {
    val content = this
        .replace("\n", "")
        .replace("\r\n", "")
    return URLEncoder.encode(content, Charsets.UTF_8).take(100)
}

パースします。contentTypeを確認してパースしていきます。
ネストしているMimeMultipartについては、入っているものは同じ様な内容でした。(text/plainとtext/htmlとか)

object MultipartParser {
    fun parseMultipart(multipart: MimeMultipart, indent: Int) {
        (0 until multipart.count).map { index ->
            multipart.getBodyPart(index)
        }.mapIndexed { index, bodyPart ->
            println("${indent(indent)}Miltipart: $indent-$index")
            parse(indent, bodyPart)
        }
    }

    private fun parse(indent: Int, bodyPart: BodyPart) {
        when {
            bodyPart.isMimeType("text/plain") -> {
                println(
                    "${indent(indent)}${bodyPart.contentType}: ${bodyPart.content.toString().toPreviewString()}"
                )
            }

            bodyPart.isMimeType("text/html") -> {
                println(
                    "${indent(indent)}${bodyPart.contentType}: ${bodyPart.content.toString().toPreviewString()}"
                )
            }

            bodyPart.isMimeType("image/jpeg") -> {
                when(val content = bodyPart.content) {
                    is BASE64DecoderStream -> {
                        println("${indent(indent)}${bodyPart.contentType}: ${content}")
                    }
                    else -> {
                        println("${indent(indent)}${bodyPart.contentType}: Unknown Class: ${content::class.java}")
                    }
                }
            }

            bodyPart.content is MimeMultipart -> {
                println("${indent(indent)}->MimeMultipart")
                parseMultipart(bodyPart.content as MimeMultipart, indent + 1)
            }

            else -> {
                println("${indent(indent)}->Unknown:${bodyPart.contentType} ${bodyPart.content.toString().toPreviewString()}")
            }
        }
    }

    private fun indent(level: Int) = "\t".repeat(level)
}