1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-25 04:50:26 +08:00
This commit is contained in:
Him188 2020-02-06 11:33:49 +08:00
parent e53dcfd26b
commit 52dc100b19
5 changed files with 134 additions and 50 deletions
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network
mirai-core/src/commonMain/kotlin/net.mamoe.mirai
mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman

View File

@ -154,12 +154,12 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
val groupInfo = mutableMapOf<Long, Int>()
try {
bot.logger.info("开始加载群组列表与群成员列表")
val troopData = FriendList.GetTroopListSimplify(
val troopListData = FriendList.GetTroopListSimplify(
bot.client
).sendAndExpect<FriendList.GetTroopListSimplify.Response>(timeoutMillis = 5000)
// println("获取到群数量" + troopData.groups.size)
val toGet: MutableMap<GroupImpl, ContactList<Member>> = mutableMapOf()
troopData.groups.forEach {
troopListData.groups.forEach {
val contactList = ContactList(LockFreeLinkedList<Member>())
val groupInfoResponse = try {
TroopManagement.GetGroupOperationInfo(
@ -204,7 +204,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
}
}
}
bot.logger.info("群组列表与群成员加载完成, 共 ${troopData.groups.size}")
bot.logger.info("群组列表与群成员加载完成, 共 ${troopListData.groups.size}")
} catch (e: Exception) {
bot.logger.error("加载组信息失败|一般这是由于加载过于频繁导致/将以热加载方式加载群列表")
println(e.message)

View File

@ -6,6 +6,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.io.OutputStream
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.use
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.ContactList
@ -14,23 +15,33 @@ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.LoginFailedException
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.WeakRef
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.transferTo
/**
* 机器人对象. 一个机器人实例登录一个 QQ 账号.
* Mirai 为多账号设计, 可同时维护多个机器人.
*
* : Bot 为全协程实现, 没有其他任务时若不使用 [awaitDisconnection], 主线程将会退出.
*
* @see Contact
*/
abstract class Bot : CoroutineScope {
@UseExperimental(MiraiInternalAPI::class)
companion object {
/**
* 复制一份此时的 [Bot] 实例列表.
*/
val instances: List<WeakRef<Bot>> get() = BotImpl.instances.toList()
/**
* 遍历每一个 [Bot] 实例
*/
inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
/**
* 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException]
*/
fun instanceWhose(qq: Long): Bot = BotImpl.instanceWhose(qq = qq)
}
@ -53,7 +64,7 @@ abstract class Bot : CoroutineScope {
// region contacts
/**
* 机器人的好友列表.
* 机器人的好友列表. 它将与服务器同步更新
*/
abstract val qqs: ContactList<QQ>
@ -63,6 +74,22 @@ abstract class Bot : CoroutineScope {
@Deprecated(message = "这个函数有歧义. 它获取的是好友, 却名为 getQQ", replaceWith = ReplaceWith("getFriend(id)"))
fun getQQ(id: Long): QQ = getFriend(id)
/**
* 获取一个好友或一个群.
* 在一些情况下这可能会造成歧义. 请考虑后使用.
*/
operator fun get(id: Long): Contact {
return this.qqs.getOrNull(id) ?: this.groups.getOrNull(id) ?: throw NoSuchElementException("contact id $id")
}
/**
* 判断是否有这个 id 的好友或群.
* 在一些情况下这可能会造成歧义. 请考虑后使用.
*/
operator fun contains(id: Long): Boolean {
return this.qqs.contains(id) || this.groups.contains(id)
}
/**
* 获取一个好友对象. 若没有这个好友, 则会抛出异常 [NoSuchElementException]
*/
@ -86,7 +113,7 @@ abstract class Bot : CoroutineScope {
*/
abstract fun getGroup(id: Long): Group
// 目前还不能构造群对象. 这将在以后支持
// TODO 目前还不能构造群对象. 这将在以后支持
// endregion
@ -97,6 +124,11 @@ abstract class Bot : CoroutineScope {
*/
abstract val network: BotNetworkHandler
/**
* 挂起直到 [Bot] 下线.
*/
suspend inline fun awaitDisconnection() = network.awaitDisconnection()
/**
* 登录, 或重新登录.
* 不建议调用这个函数.
@ -111,8 +143,12 @@ abstract class Bot : CoroutineScope {
// region actions
@Deprecated("内存使用效率十分低下", ReplaceWith("this.download()"), DeprecationLevel.WARNING)
abstract suspend fun Image.downloadAsByteArray(): ByteArray
/**
* 将图片下载到内存中 (使用 [IoBuffer.Pool])
*/
abstract suspend fun Image.download(): ByteReadPacket
/**
@ -131,7 +167,9 @@ abstract class Bot : CoroutineScope {
// endregion
/**
* 关闭这个 [Bot], 停止一切相关活动. 不可重新登录.
* 关闭这个 [Bot], 停止一切相关活动. 所有引用都会被释放.
*
* : 不可重新登录. 必须重新实例化一个 [Bot].
*
* @param cause 原因. null 时视为正常关闭, null 时视为异常关闭
*/
@ -139,9 +177,14 @@ abstract class Bot : CoroutineScope {
// region extensions
@Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("getFriend(this.toLong())"))
fun Int.qq(): QQ = getFriend(this.toLong())
@Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("getFriend(this)"))
fun Long.qq(): QQ = getFriend(this)
final override fun toString(): String {
return "Bot(${uin})"
}
/**
* 需要调用者自行 close [output]

View File

@ -33,29 +33,29 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
final override val logger: MiraiLogger by lazy { configuration.logger ?: DefaultLogger("Bot($uin)").also { configuration.logger = it } }
init {
@Suppress("LeakingThis")
instances.addLast(this)
instances.addLast(this.weakRef())
}
companion object {
@PublishedApi
internal val instances: LockFreeLinkedList<Bot> = LockFreeLinkedList()
internal val instances: LockFreeLinkedList<WeakRef<Bot>> = LockFreeLinkedList()
inline fun forEachInstance(block: (Bot) -> Unit) = instances.forEach(block)
inline fun forEachInstance(block: (Bot) -> Unit) = instances.forEach {
it.get()?.let(block)
}
fun instanceWhose(qq: Long): Bot {
instances.forEach {
@Suppress("PropertyName")
if (it.uin == qq) {
return it
it.get()?.let { bot ->
if (bot.uin == qq) {
return bot
}
}
}
throw NoSuchElementException()
}
}
final override fun toString(): String = "Bot(${uin})"
// region network
final override val network: N get() = _network
@ -134,16 +134,17 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
@UseExperimental(MiraiInternalAPI::class)
override fun close(cause: Throwable?) {
if (cause == null) {
network.close()
this.botJob.complete()
groups.delegate.clear()
qqs.delegate.clear()
} else {
network.close(cause)
this.botJob.completeExceptionally(cause)
groups.delegate.clear()
qqs.delegate.clear()
kotlin.runCatching {
if (cause == null) {
network.close()
this.botJob.complete()
} else {
network.close(cause)
this.botJob.completeExceptionally(cause)
}
}
groups.delegate.clear()
qqs.delegate.clear()
instances.removeIf { it.get()?.uin == this.uin }
}
}

View File

@ -117,7 +117,7 @@ open class LockFreeLinkedList<E> {
/**
* 过滤并获取, 获取不到则添加一个元素.
*/
inline fun filteringGetOrAdd(filter: (E) -> Boolean, noinline supplier: () -> E): E {
fun filteringGetOrAdd(filter: (E) -> Boolean, supplier: () -> E): E {
val node = LazyNode(tail, supplier)
while (true) {
@ -152,6 +152,36 @@ open class LockFreeLinkedList<E> {
}, { it !is Tail })
}.dropLast(4)
@Suppress("DuplicatedCode")
fun removeIf(filter: (E) -> Boolean) {
while (true) {
val before = head.iterateBeforeFirst { it.isValidElementNode() && filter(it.nodeValue) }
val toRemove = before.nextNode
if (toRemove === tail) {
return
}
if (toRemove.isRemoved()) {
continue
}
@Suppress("BooleanLiteralArgument") // false positive
if (!toRemove.removed.compareAndSet(false, true)) {
// logically remove: all the operations will recognize this node invalid
continue
}
// physically remove: try to fix the link
var next: Node<E> = toRemove.nextNode
while (next !== tail && next.isRemoved()) {
next = next.nextNode
}
if (before.nextNodeRef.compareAndSet(toRemove, next)) {
return
}
}
}
@Suppress("DuplicatedCode")
open fun remove(element: E): Boolean {
while (true) {
val before = head.iterateBeforeNodeValue(element)
@ -162,6 +192,7 @@ open class LockFreeLinkedList<E> {
if (toRemove.isRemoved()) {
continue
}
@Suppress("BooleanLiteralArgument") // false positive
if (!toRemove.removed.compareAndSet(false, true)) {
// logically remove: all the operations will recognize this node invalid
continue

View File

@ -1,13 +1,13 @@
package demo.gentleman
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import kotlinx.coroutines.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.uploadAsImage
import org.jsoup.Jsoup
import kotlin.random.Random
class GentleImage(val contact: Contact, val keyword: String) {
@ -15,34 +15,43 @@ class GentleImage(val contact: Contact, val keyword: String) {
val seImage: Deferred<Image> by lazy { getImage(1) }
@UseExperimental(UnstableDefault::class)
fun getImage(r18: Int): Deferred<Image> {
return GlobalScope.async {
withTimeoutOrNull(10 * 1000) {
withTimeoutOrNull(20 * 1000) {
withContext(Dispatchers.IO) {
@Serializable
data class Setu(
val url: String,
val pid: String
)
@Serializable
data class Result(
val data: List<Setu>
)
val result =
JSON.parseObject(
Jsoup.connect("https://api.lolicon.app/setu/?r18=$r18" + if (keyword.isNotBlank()) "&keyword=$keyword&num=100" else "").ignoreContentType(
true
).timeout(
10_0000
).get().body().text()
Json.plain.parse(
Result.serializer(),
Jsoup.connect("https://api.lolicon.app/setu/?r18=$r18" + if (keyword.isNotBlank()) "&keyword=$keyword&num=10" else "")
.ignoreContentType(true)
.userAgent(UserAgent.randomUserAgent)
.proxy("127.0.0.1", 1088)
.timeout(10_0000)
.get().body().text()
)
val url: String
val pid: String
val data = result.getJSONArray("data")
with(JSONObject(data.getJSONObject(Random.nextInt(0, data.size)))) {
url = this.getString("url")
pid = this.getString("pid")
}
val setu = result.data.random()
Jsoup
.connect(url)
.connect(setu.url)
.followRedirects(true)
.timeout(180_000)
.ignoreContentType(true)
.userAgent("Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; ja-jp) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27")
.referrer("https://www.pixiv.net/member_illust.php?mode=medium&illust_id=$pid")
.referrer("https://www.pixiv.net/member_illust.php?mode=medium&illust_id=${setu.pid}")
.proxy("127.0.0.1", 1088)
.ignoreHttpErrors(true)
.maxBodySize(10000000)
.execute().also { check(it.statusCode() == 200) { "Failed to download image" } }