2016-12-29 06:50:18 +01:00
|
|
|
package org.mariotaku.twidere.extension.model
|
2016-12-07 07:15:12 +01:00
|
|
|
|
|
|
|
import android.content.Context
|
|
|
|
import android.net.Uri
|
2016-12-08 08:33:13 +01:00
|
|
|
import android.text.TextUtils
|
2016-12-08 04:07:13 +01:00
|
|
|
import com.bluelinelabs.logansquare.LoganSquare
|
|
|
|
import com.nostra13.universalimageloader.utils.IoUtils
|
2016-12-07 07:15:12 +01:00
|
|
|
import org.apache.james.mime4j.dom.Header
|
|
|
|
import org.apache.james.mime4j.dom.MessageServiceFactory
|
|
|
|
import org.apache.james.mime4j.dom.address.Mailbox
|
|
|
|
import org.apache.james.mime4j.dom.field.*
|
2016-12-08 04:07:13 +01:00
|
|
|
import org.apache.james.mime4j.message.*
|
2016-12-07 07:15:12 +01:00
|
|
|
import org.apache.james.mime4j.parser.MimeStreamParser
|
|
|
|
import org.apache.james.mime4j.storage.StorageBodyFactory
|
|
|
|
import org.apache.james.mime4j.stream.BodyDescriptor
|
|
|
|
import org.apache.james.mime4j.stream.MimeConfig
|
2016-12-08 04:07:13 +01:00
|
|
|
import org.apache.james.mime4j.stream.RawField
|
|
|
|
import org.apache.james.mime4j.util.MimeUtil
|
2016-12-07 07:15:12 +01:00
|
|
|
import org.mariotaku.ktextension.toInt
|
|
|
|
import org.mariotaku.ktextension.toString
|
2016-12-08 08:33:13 +01:00
|
|
|
import org.mariotaku.twidere.R
|
2016-12-08 04:07:13 +01:00
|
|
|
import org.mariotaku.twidere.model.*
|
|
|
|
import org.mariotaku.twidere.model.Draft.Action
|
|
|
|
import org.mariotaku.twidere.model.draft.SendDirectMessageActionExtras
|
|
|
|
import org.mariotaku.twidere.model.draft.UpdateStatusActionExtras
|
2016-12-07 07:15:12 +01:00
|
|
|
import org.mariotaku.twidere.util.collection.NonEmptyHashMap
|
2016-12-08 04:07:13 +01:00
|
|
|
import java.io.File
|
|
|
|
import java.io.FileOutputStream
|
2016-12-07 07:15:12 +01:00
|
|
|
import java.io.InputStream
|
|
|
|
import java.io.OutputStream
|
|
|
|
import java.nio.charset.Charset
|
|
|
|
import java.util.*
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Created by mariotaku on 2016/12/7.
|
|
|
|
*/
|
|
|
|
fun Draft.writeMimeMessageTo(context: Context, st: OutputStream) {
|
|
|
|
val bodyFactory = StorageBodyFactory()
|
|
|
|
val storageProvider = bodyFactory.storageProvider
|
|
|
|
val contentResolver = context.contentResolver
|
|
|
|
|
|
|
|
val factory = MessageServiceFactory.newInstance()
|
|
|
|
val builder = factory.newMessageBuilder()
|
|
|
|
val writer = factory.newMessageWriter()
|
|
|
|
|
|
|
|
val message = builder.newMessage() as AbstractMessage
|
|
|
|
|
|
|
|
message.date = Date(this.timestamp)
|
2016-12-08 08:33:13 +01:00
|
|
|
message.subject = this.getActionName(context)
|
2016-12-07 07:15:12 +01:00
|
|
|
message.setFrom(this.account_keys?.map { Mailbox(it.id, it.host) })
|
2016-12-08 08:33:13 +01:00
|
|
|
message.setTo(message.from)
|
2016-12-08 04:07:13 +01:00
|
|
|
if (message.header == null) {
|
|
|
|
message.header = HeaderImpl()
|
|
|
|
}
|
|
|
|
|
|
|
|
this.location?.let { location ->
|
|
|
|
message.header.addField(RawField("X-GeoLocation", location.toString()))
|
|
|
|
}
|
|
|
|
this.action_type?.let { type ->
|
|
|
|
message.header.addField(RawField("X-Action-Type", type))
|
|
|
|
}
|
2016-12-07 07:15:12 +01:00
|
|
|
|
|
|
|
val multipart = MultipartImpl("mixed")
|
|
|
|
multipart.addBodyPart(BodyPart().apply {
|
2016-12-08 04:07:13 +01:00
|
|
|
setText(bodyFactory.textBody(this@writeMimeMessageTo.text, Charsets.UTF_8.name()))
|
2016-12-07 07:15:12 +01:00
|
|
|
})
|
2016-12-08 04:07:13 +01:00
|
|
|
|
|
|
|
this.action_extras?.let { extras ->
|
|
|
|
multipart.addBodyPart(BodyPart().apply {
|
|
|
|
setText(bodyFactory.textBody(LoganSquare.serialize(extras)), "json")
|
|
|
|
this.filename = "twidere.action.extras.json"
|
|
|
|
})
|
|
|
|
}
|
2016-12-07 07:15:12 +01:00
|
|
|
this.media?.forEach { mediaItem ->
|
|
|
|
multipart.addBodyPart(BodyPart().apply {
|
|
|
|
val uri = Uri.parse(mediaItem.uri)
|
|
|
|
val mimeType = mediaItem.getMimeType(contentResolver) ?: "application/octet-stream"
|
|
|
|
val parameters = NonEmptyHashMap<String, String?>()
|
|
|
|
parameters["alt_text"] = mediaItem.alt_text
|
|
|
|
parameters["media_type"] = mediaItem.type.toString()
|
2017-01-04 04:44:18 +01:00
|
|
|
val storage = contentResolver.openInputStream(uri).use { storageProvider.store(it) }
|
2016-12-07 07:15:12 +01:00
|
|
|
this.filename = uri.lastPathSegment
|
2016-12-08 04:07:13 +01:00
|
|
|
this.contentTransferEncoding = MimeUtil.ENC_BASE64
|
|
|
|
this.setBody(bodyFactory.binaryBody(storage), mimeType, parameters)
|
2016-12-07 07:15:12 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
message.setMultipart(multipart)
|
|
|
|
writer.writeMessage(message, st)
|
2016-12-08 04:07:13 +01:00
|
|
|
st.flush()
|
2016-12-07 07:15:12 +01:00
|
|
|
}
|
|
|
|
|
2016-12-31 18:13:21 +01:00
|
|
|
fun Draft.readMimeMessageFrom(context: Context, st: InputStream): Boolean {
|
2016-12-07 07:15:12 +01:00
|
|
|
val config = MimeConfig()
|
|
|
|
val parser = MimeStreamParser(config)
|
2016-12-08 04:07:13 +01:00
|
|
|
parser.isContentDecoding = true
|
2016-12-31 18:13:21 +01:00
|
|
|
val handler = DraftContentHandler(context, this)
|
|
|
|
parser.setContentHandler(handler)
|
2016-12-07 07:15:12 +01:00
|
|
|
parser.parse(st)
|
2016-12-31 18:13:21 +01:00
|
|
|
return !handler.malformedData
|
2016-12-07 07:15:12 +01:00
|
|
|
}
|
|
|
|
|
2016-12-08 08:33:13 +01:00
|
|
|
fun Draft.getActionName(context: Context): String? {
|
|
|
|
if (TextUtils.isEmpty(action_type)) return context.getString(R.string.update_status)
|
|
|
|
when (action_type) {
|
2017-02-07 15:55:36 +01:00
|
|
|
Draft.Action.UPDATE_STATUS, Draft.Action.UPDATE_STATUS_COMPAT_1,
|
|
|
|
Draft.Action.UPDATE_STATUS_COMPAT_2 -> {
|
2016-12-08 08:33:13 +01:00
|
|
|
return context.getString(R.string.update_status)
|
|
|
|
}
|
|
|
|
Draft.Action.REPLY -> {
|
2016-12-26 17:26:09 +01:00
|
|
|
return context.getString(R.string.action_reply)
|
2016-12-08 08:33:13 +01:00
|
|
|
}
|
|
|
|
Draft.Action.QUOTE -> {
|
2016-12-26 17:26:09 +01:00
|
|
|
return context.getString(R.string.action_quote)
|
2016-12-08 08:33:13 +01:00
|
|
|
}
|
2017-02-07 15:55:36 +01:00
|
|
|
Draft.Action.FAVORITE -> {
|
|
|
|
return context.getString(R.string.action_favorite)
|
|
|
|
}
|
|
|
|
Draft.Action.RETWEET -> {
|
|
|
|
return context.getString(R.string.action_retweet)
|
|
|
|
}
|
2016-12-08 08:33:13 +01:00
|
|
|
Draft.Action.SEND_DIRECT_MESSAGE, Draft.Action.SEND_DIRECT_MESSAGE_COMPAT -> {
|
|
|
|
return context.getString(R.string.send_direct_message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2016-12-31 18:13:21 +01:00
|
|
|
val Draft.filename: String get() = "$unique_id_non_null.eml"
|
|
|
|
|
|
|
|
val Draft.unique_id_non_null: String
|
|
|
|
get() = unique_id ?: UUID.nameUUIDFromBytes(("$_id:$timestamp").toByteArray()).toString()
|
|
|
|
|
2016-12-08 04:07:13 +01:00
|
|
|
private class DraftContentHandler(private val context: Context, private val draft: Draft) : SimpleContentHandler() {
|
2016-12-07 07:15:12 +01:00
|
|
|
private val processingStack = Stack<SimpleContentHandler>()
|
|
|
|
private val mediaList: MutableList<ParcelableMediaUpdate> = ArrayList()
|
2016-12-31 18:13:21 +01:00
|
|
|
|
|
|
|
internal var malformedData: Boolean = false
|
2016-12-07 07:15:12 +01:00
|
|
|
override fun headers(header: Header) {
|
|
|
|
if (processingStack.isEmpty()) {
|
2017-02-17 11:42:39 +01:00
|
|
|
draft.timestamp = header.getField("Date")?.let {
|
2016-12-07 07:15:12 +01:00
|
|
|
(it as DateTimeField).date.time
|
2016-12-08 04:07:13 +01:00
|
|
|
} ?: 0
|
2017-02-17 11:42:39 +01:00
|
|
|
draft.account_keys = header.getField("From")?.let { field ->
|
2016-12-07 07:15:12 +01:00
|
|
|
when (field) {
|
|
|
|
is MailboxField -> {
|
2017-02-17 11:42:39 +01:00
|
|
|
return@let arrayOf(field.mailbox.let { UserKey(it.localPart, it.domain) })
|
2016-12-07 07:15:12 +01:00
|
|
|
}
|
|
|
|
is MailboxListField -> {
|
2017-02-17 11:42:39 +01:00
|
|
|
return@let field.mailboxList.map { UserKey(it.localPart, it.domain) }.toTypedArray()
|
2016-12-07 07:15:12 +01:00
|
|
|
}
|
|
|
|
else -> {
|
2017-02-17 11:42:39 +01:00
|
|
|
return@let null
|
2016-12-07 07:15:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-02-17 11:42:39 +01:00
|
|
|
draft.location = header.getField("X-GeoLocation")?.body?.let(ParcelableLocation::valueOf)
|
2016-12-08 04:07:13 +01:00
|
|
|
draft.action_type = header.getField("X-Action-Type")?.body
|
2016-12-07 07:15:12 +01:00
|
|
|
} else {
|
|
|
|
processingStack.peek().headers(header)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun startMultipart(bd: BodyDescriptor) {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun preamble(`is`: InputStream?) {
|
|
|
|
processingStack.peek().preamble(`is`)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun startBodyPart() {
|
2016-12-08 04:07:13 +01:00
|
|
|
processingStack.push(BodyPartHandler(context, draft))
|
2016-12-07 07:15:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun body(bd: BodyDescriptor?, `is`: InputStream?) {
|
2016-12-31 18:13:21 +01:00
|
|
|
if (processingStack.isEmpty()) {
|
|
|
|
malformedData = true
|
|
|
|
return
|
|
|
|
}
|
2016-12-07 07:15:12 +01:00
|
|
|
processingStack.peek().body(bd, `is`)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun endBodyPart() {
|
|
|
|
val handler = processingStack.pop() as BodyPartHandler
|
|
|
|
handler.media?.let {
|
|
|
|
mediaList.add(it)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun epilogue(`is`: InputStream?) {
|
|
|
|
processingStack.peek().epilogue(`is`)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun endMultipart() {
|
|
|
|
draft.media = mediaList.toTypedArray()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-08 04:07:13 +01:00
|
|
|
private class BodyPartHandler(private val context: Context, private val draft: Draft) : SimpleContentHandler() {
|
2016-12-07 07:15:12 +01:00
|
|
|
internal lateinit var header: Header
|
|
|
|
internal var media: ParcelableMediaUpdate? = null
|
|
|
|
|
|
|
|
override fun headers(header: Header) {
|
|
|
|
this.header = header
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun body(bd: BodyDescriptor, st: InputStream) {
|
|
|
|
body(header, bd, st)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun body(header: Header, bd: BodyDescriptor, st: InputStream) {
|
|
|
|
val contentDisposition = header.getField("Content-Disposition") as? ContentDispositionField
|
|
|
|
if (contentDisposition != null && contentDisposition.isAttachment) {
|
|
|
|
when (contentDisposition.filename) {
|
2016-12-08 04:07:13 +01:00
|
|
|
"twidere.action.extras.json" -> {
|
|
|
|
draft.action_extras = when (draft.action_type) {
|
|
|
|
"0", "1", Action.UPDATE_STATUS, Action.REPLY, Action.QUOTE -> {
|
|
|
|
LoganSquare.parse(st, UpdateStatusActionExtras::class.java)
|
|
|
|
}
|
|
|
|
"2", Action.SEND_DIRECT_MESSAGE -> {
|
|
|
|
LoganSquare.parse(st, SendDirectMessageActionExtras::class.java)
|
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-12-07 07:15:12 +01:00
|
|
|
else -> {
|
|
|
|
val contentType = header.getField("Content-Type") as? ContentTypeField
|
2016-12-08 04:07:13 +01:00
|
|
|
val filename = contentDisposition.filename ?: return
|
|
|
|
val mediaFile = File(context.filesDir, filename)
|
2016-12-07 07:15:12 +01:00
|
|
|
media = ParcelableMediaUpdate().apply {
|
2016-12-08 04:07:13 +01:00
|
|
|
bd.transferEncoding
|
2016-12-07 07:15:12 +01:00
|
|
|
this.type = contentType?.getParameter("media_type").toInt(ParcelableMedia.Type.UNKNOWN)
|
|
|
|
this.alt_text = contentType?.getParameter("alt_text")
|
2016-12-08 04:07:13 +01:00
|
|
|
FileOutputStream(mediaFile).use {
|
|
|
|
IoUtils.copyStream(st, it, null)
|
|
|
|
it.flush()
|
|
|
|
}
|
|
|
|
this.uri = Uri.fromFile(mediaFile).toString()
|
2016-12-07 07:15:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (bd.mimeType == "text/plain" && draft.text == null) {
|
|
|
|
draft.text = st.toString(Charset.forName(bd.charset))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-07 11:02:32 +01:00
|
|
|
|
|
|
|
fun draftActionTypeString(@Draft.Action action: String?): String {
|
|
|
|
return when (action) {
|
|
|
|
Draft.Action.QUOTE -> "quote"
|
|
|
|
Draft.Action.REPLY -> "reply"
|
|
|
|
else -> "tweet"
|
|
|
|
}
|
|
|
|
}
|