Commit 692e7c95 authored by Him188's avatar Him188

Image download support, close #49

parent bf1e75eb
...@@ -9,7 +9,9 @@ ...@@ -9,7 +9,9 @@
package net.mamoe.mirai.qqandroid package net.mamoe.mirai.qqandroid
import kotlinx.io.core.ByteReadPacket import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse
import io.ktor.utils.io.ByteReadChannel
import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.BotImpl import net.mamoe.mirai.BotImpl
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
...@@ -17,10 +19,9 @@ import net.mamoe.mirai.data.AddFriendResult ...@@ -17,10 +19,9 @@ import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.data.FriendInfo import net.mamoe.mirai.data.FriendInfo
import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.data.GroupInfo
import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.qqandroid.message.CustomFaceFromServer
import net.mamoe.mirai.message.data.messageRandom import net.mamoe.mirai.qqandroid.message.NotOnlineImageFromServer
import net.mamoe.mirai.message.data.sequenceId
import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
...@@ -150,13 +151,20 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -150,13 +151,20 @@ internal abstract class QQAndroidBotBase constructor(
} }
} }
override suspend fun Image.download(): ByteReadPacket { override suspend fun Image.url(): String = "http://gchat.qpic.cn" + when (this) {
TODO("not implemented") is NotOnlineImageFromServer -> this.delegate.origUrl
is CustomFaceFromServer -> this.delegate.origUrl
is CustomFaceFromFile -> {
TODO()
}
is NotOnlineImageFromFile -> {
TODO()
}
else -> error("unsupported image class: ${this::class.simpleName}")
} }
@Suppress("OverridingDeprecatedMember") override suspend fun Image.channel(): ByteReadChannel {
override suspend fun Image.downloadAsByteArray(): ByteArray { return Http.get<HttpResponse>(url()).content
TODO("not implemented")
} }
override suspend fun approveFriendAddRequest(id: Long, remark: String?) { override suspend fun approveFriendAddRequest(id: Long, remark: String?) {
......
...@@ -11,14 +11,11 @@ ...@@ -11,14 +11,11 @@
package net.mamoe.mirai package net.mamoe.mirai
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.io.OutputStream
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.use
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.data.FriendInfo import net.mamoe.mirai.data.FriendInfo
...@@ -30,7 +27,6 @@ import net.mamoe.mirai.message.data.MessageSource ...@@ -30,7 +27,6 @@ import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.transferTo
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmStatic import kotlin.jvm.JvmStatic
...@@ -226,11 +222,12 @@ abstract class Bot : CoroutineScope { ...@@ -226,11 +222,12 @@ abstract class Bot : CoroutineScope {
abstract suspend fun recall(source: MessageSource) abstract suspend fun recall(source: MessageSource)
/** /**
* 撤回这条消息. * 撤回一条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息.
* *
* [Bot] 撤回自己的消息不需要权限. * [Bot] 撤回自己的消息不需要权限.
* [Bot] 撤回群员的消息需要管理员权限. * [Bot] 撤回群员的消息需要管理员权限.
* *
* @param senderId 这条消息的发送人. 可以为 [Bot.uin] 或 [Member.id]
* @param messageId 即 [MessageSource.id] * @param messageId 即 [MessageSource.id]
* *
* @throws PermissionDeniedException 当 [Bot] 无权限操作时 * @throws PermissionDeniedException 当 [Bot] 无权限操作时
...@@ -239,15 +236,18 @@ abstract class Bot : CoroutineScope { ...@@ -239,15 +236,18 @@ abstract class Bot : CoroutineScope {
*/ */
abstract suspend fun recall(groupId: Long, senderId: Long, messageId: Long) abstract suspend fun recall(groupId: Long, senderId: Long, messageId: Long)
@Deprecated("内存使用效率十分低下", ReplaceWith("this.download()"), DeprecationLevel.WARNING) /**
@MiraiExperimentalAPI("未支持") * 获取图片下载链接
abstract suspend fun Image.downloadAsByteArray(): ByteArray */
abstract suspend fun Image.url(): String
/** /**
* 将图片下载到内存中 (使用 [IoBuffer.Pool]) * 获取图片下载链接并开始下载.
*
* @see ByteReadChannel.copyAndClose
* @see ByteReadChannel.copyTo
*/ */
@MiraiExperimentalAPI("未支持") abstract suspend fun Image.channel(): ByteReadChannel
abstract suspend fun Image.download(): ByteReadPacket
/** /**
* 添加一个好友 * 添加一个好友
...@@ -278,20 +278,7 @@ abstract class Bot : CoroutineScope { ...@@ -278,20 +278,7 @@ abstract class Bot : CoroutineScope {
*/ */
abstract fun close(cause: Throwable? = null) abstract fun close(cause: Throwable? = null)
// region extensions final override fun toString(): String = "Bot(${uin})"
final override fun toString(): String {
return "Bot(${uin})"
}
/**
* 需要调用者自行 close [output]
*/
@MiraiExperimentalAPI("未支持")
suspend inline fun Image.downloadTo(output: OutputStream) =
download().use { input -> input.transferTo(output) }
// endregion
} }
/** /**
......
...@@ -11,9 +11,7 @@ ...@@ -11,9 +11,7 @@
package net.mamoe.mirai.message package net.mamoe.mirai.message
import kotlinx.io.core.ByteReadPacket import io.ktor.utils.io.ByteReadChannel
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
...@@ -128,20 +126,22 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : Packet, Bot ...@@ -128,20 +126,22 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : Packet, Bot
// endregion // endregion
// region 下载图片 // region 下载图片
/** /**
* 将图片下载到内存. * 获取图片下载链接
* *
* 非常不推荐这样做. * @return "http://gchat.qpic.cn/gchatpic_new/..."
*/ */
@Deprecated("内存使用效率十分低下", ReplaceWith("this.download()"), DeprecationLevel.WARNING) suspend inline fun Image.url(): String = bot.run { url() }
suspend inline fun Image.downloadAsByteArray(): ByteArray = bot.run { download().readBytes() }
// TODO: 2020/2/5 为下载图片添加文件系统的存储方式
/** /**
* 将图片下载到内存缓存中 (使用 [IoBuffer.Pool]) * 获取图片下载链接并开始下载.
*
* @see ByteReadChannel.copyAndClose
* @see ByteReadChannel.copyTo
*/ */
suspend inline fun Image.download(): ByteReadPacket = bot.run { download() } suspend inline fun Image.channel(): ByteReadChannel = bot.run { channel() }
// endregion // endregion
} }
......
...@@ -16,7 +16,6 @@ package net.mamoe.mirai.utils ...@@ -16,7 +16,6 @@ package net.mamoe.mirai.utils
import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.readAvailable import io.ktor.utils.io.readAvailable
import kotlinx.coroutines.io.close
import kotlinx.io.OutputStream import kotlinx.io.OutputStream
import kotlinx.io.core.Output import kotlinx.io.core.Output
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
...@@ -24,6 +23,7 @@ import net.mamoe.mirai.utils.io.ByteArrayPool ...@@ -24,6 +23,7 @@ import net.mamoe.mirai.utils.io.ByteArrayPool
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
// copyTo
/** /**
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
...@@ -49,6 +49,8 @@ suspend fun ByteReadChannel.copyTo(dst: Output) { ...@@ -49,6 +49,8 @@ suspend fun ByteReadChannel.copyTo(dst: Output) {
} }
} }
/* // 垃圾 kotlin, Unresolved reference: ByteWriteChannel
/** /**
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
*/ */
...@@ -60,7 +62,7 @@ suspend fun ByteReadChannel.copyTo(dst: kotlinx.coroutines.io.ByteWriteChannel) ...@@ -60,7 +62,7 @@ suspend fun ByteReadChannel.copyTo(dst: kotlinx.coroutines.io.ByteWriteChannel)
} while (size != 0) } while (size != 0)
} }
} }
*/
// copyAndClose // copyAndClose
...@@ -97,18 +99,19 @@ suspend fun ByteReadChannel.copyAndClose(dst: Output) { ...@@ -97,18 +99,19 @@ suspend fun ByteReadChannel.copyAndClose(dst: Output) {
} }
} }
/*// 垃圾 kotlin, Unresolved reference: ByteWriteChannel
/** /**
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
*/ */
suspend fun ByteReadChannel.copyAndClose(dst: kotlinx.coroutines.io.ByteWriteChannel) { suspend fun ByteReadChannel.copyAndClose(dst: kotlinx.coroutines.io.ByteWriteChannel) {
try { dst.close(kotlin.runCatching {
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
do { do {
val size = this.readAvailable(it) val size = this.readAvailable(it)
dst.writeFully(it, 0, size) dst.writeFully(it, 0, size)
} while (size != 0) } while (size != 0)
} }
} finally { }.exceptionOrNull())
dst.close()
}
} }
*/
\ No newline at end of file
...@@ -11,23 +11,19 @@ ...@@ -11,23 +11,19 @@
package net.mamoe.mirai.message package net.mamoe.mirai.message
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.Input import kotlinx.io.core.Input
import kotlinx.io.core.use import kotlinx.io.core.use
import kotlinx.io.streams.inputStream
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.toExternalImage import net.mamoe.mirai.utils.copyAndClose
import net.mamoe.mirai.utils.copyTo
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.net.URL import java.net.URL
import javax.imageio.ImageIO
/** /**
* 一条从服务器接收到的消息事件. * 一条从服务器接收到的消息事件.
...@@ -72,16 +68,22 @@ actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual con ...@@ -72,16 +68,22 @@ actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual con
// endregion 发送图片 (扩展) // endregion 发送图片 (扩展)
// region 下载图片 (扩展) // region 下载图片 (扩展)
suspend inline fun Image.downloadTo(file: File): Long = file.outputStream().use { downloadTo(it) } suspend inline fun Image.downloadTo(file: File) = file.outputStream().use { downloadTo(it) }
/** /**
* 这个函数结束后不会关闭 [output]. 请务必解决好 [OutputStream.close] * 下载图片到 [output] 但不关闭这个 [output]
*/ */
suspend inline fun Image.downloadTo(output: OutputStream): Long = suspend inline fun Image.downloadTo(output: OutputStream) = channel().copyTo(output)
download().inputStream().use { input -> withContext(Dispatchers.IO) { input.copyTo(output) } }
suspend inline fun Image.downloadAsStream(): InputStream = download().inputStream() /**
suspend inline fun Image.downloadAsExternalImage(): ExternalImage = withContext(Dispatchers.IO) { download().toExternalImage() } * 下载图片到 [output] 并关闭这个 [output]
*/
suspend inline fun Image.downloadAndClose(output: OutputStream) = channel().copyAndClose(output)
/*
suspend inline fun Image.downloadAsStream(): InputStream = channel().asInputStream()
suspend inline fun Image.downloadAsExternalImage(): ExternalImage = withContext(Dispatchers.IO) { downloadAsStream().toExternalImage() }
suspend inline fun Image.downloadAsBufferedImage(): BufferedImage = withContext(Dispatchers.IO) { ImageIO.read(downloadAsStream()) } suspend inline fun Image.downloadAsBufferedImage(): BufferedImage = withContext(Dispatchers.IO) { ImageIO.read(downloadAsStream()) }
*/
// endregion // endregion
} }
\ No newline at end of file
package net.mamoe.mirai.japt; package net.mamoe.mirai.japt;
import kotlinx.io.core.ByteReadPacket;
import net.mamoe.mirai.Bot; import net.mamoe.mirai.Bot;
import net.mamoe.mirai.BotAccount; import net.mamoe.mirai.BotAccount;
import net.mamoe.mirai.BotFactoryJvmKt; import net.mamoe.mirai.BotFactoryJvmKt;
...@@ -153,18 +152,16 @@ public interface BlockingBot { ...@@ -153,18 +152,16 @@ public interface BlockingBot {
// region actions // region actions
@NotNull
byte[] downloadAsByteArray(@NotNull Image image);
@NotNull
ByteReadPacket download(@NotNull Image image);
/** /**
* 下载图片到 {@code outputStream}. * 下载图片到 {@code outputStream}.
* <p>
* 不会自动关闭 {@code outputStream} * 不会自动关闭 {@code outputStream}
*/ */
void download(@NotNull Image image, @NotNull OutputStream outputStream); void downloadTo(@NotNull Image image, @NotNull OutputStream outputStream);
/**
* 下载图片到 {@code outputStream} 并关闭 stream
*/
void downloadAndClose(@NotNull Image image, @NotNull OutputStream outputStream);
/** /**
* 添加一个好友 * 添加一个好友
......
...@@ -10,8 +10,6 @@ ...@@ -10,8 +10,6 @@
package net.mamoe.mirai.japt.internal package net.mamoe.mirai.japt.internal
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
...@@ -23,10 +21,7 @@ import net.mamoe.mirai.japt.BlockingGroup ...@@ -23,10 +21,7 @@ import net.mamoe.mirai.japt.BlockingGroup
import net.mamoe.mirai.japt.BlockingQQ import net.mamoe.mirai.japt.BlockingQQ
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.toList
import java.io.OutputStream import java.io.OutputStream
import java.util.stream.Stream import java.util.stream.Stream
import kotlin.streams.asStream import kotlin.streams.asStream
...@@ -59,10 +54,8 @@ internal class BlockingBotImpl(private val bot: Bot) : BlockingBot { ...@@ -59,10 +54,8 @@ internal class BlockingBotImpl(private val bot: Bot) : BlockingBot {
override fun getGroup(id: Long): BlockingGroup = runBlocking { bot.getGroup(id).blocking() } override fun getGroup(id: Long): BlockingGroup = runBlocking { bot.getGroup(id).blocking() }
override fun getNetwork(): BotNetworkHandler = bot.network override fun getNetwork(): BotNetworkHandler = bot.network
override fun login() = runBlocking { bot.login() } override fun login() = runBlocking { bot.login() }
override fun downloadAsByteArray(image: Image): ByteArray = bot.run { runBlocking { image.download().readBytes() } } override fun downloadTo(image: Image, outputStream: OutputStream) = bot.run { runBlocking { image.channel().copyTo(outputStream) } }
override fun download(image: Image): ByteReadPacket = bot.run { runBlocking { image.download() } } override fun downloadAndClose(image: Image, outputStream: OutputStream) = bot.run { runBlocking { image.channel().copyAndClose(outputStream) } }
override fun download(image: Image, outputStream: OutputStream) = bot.run { runBlocking { image.downloadTo(outputStream) } }
override fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult = runBlocking { bot.addFriend(id, message, remark) } override fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult = runBlocking { bot.addFriend(id, message, remark) }
override fun approveFriendAddRequest(id: Long, remark: String?) = runBlocking { bot.approveFriendAddRequest(id, remark) } override fun approveFriendAddRequest(id: Long, remark: String?) = runBlocking { bot.approveFriendAddRequest(id, remark) }
override fun close(throwable: Throwable?) = bot.close(throwable) override fun close(throwable: Throwable?) = bot.close(throwable)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment