diff --git a/mirai-japt/README.md b/mirai-japt/README.md new file mode 100644 index 000000000..a9627a246 --- /dev/null +++ b/mirai-japt/README.md @@ -0,0 +1,9 @@ + +# mirai-japt + +Mirai Java Apt + +提供一些阻塞/异步/RxJava API 来让 Java 调用 Mirai 的挂起函数 API 更容易 +提供 Utils 类来让 Java 调用 Mirai 的内联方法更容易 + +该模块暂未完成. \ No newline at end of file diff --git a/mirai-japt/build.gradle.kts b/mirai-japt/build.gradle.kts new file mode 100644 index 000000000..2f7cf3c43 --- /dev/null +++ b/mirai-japt/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + kotlin("jvm") + java +} + +val kotlinVersion: String by rootProject.ext +val atomicFuVersion: String by rootProject.ext +val coroutinesVersion: String by rootProject.ext +val kotlinXIoVersion: String by rootProject.ext +val coroutinesIoVersion: String by rootProject.ext +val serializationVersion: String by rootProject.ext + +val klockVersion: String by rootProject.ext +val ktorVersion: String by rootProject.ext + +kotlin { + sourceSets { + all { + languageSettings.enableLanguageFeature("InlineClasses") + languageSettings.useExperimentalAnnotation("kotlin.Experimental") + } + } +} + +fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version" + +fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version" + +dependencies { + api(project(":mirai-core")) + runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE + + api(group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-javafx", version = "1.3.2") + + api(kotlin("stdlib", kotlinVersion)) + api(kotlinx("io-jvm", kotlinXIoVersion)) + api(kotlinx("io", kotlinXIoVersion)) + api(kotlinx("coroutines-io", coroutinesIoVersion)) + api(kotlinx("coroutines-core", coroutinesVersion)) +} + +tasks.withType<JavaCompile>() { + options.encoding = "UTF-8" +} \ No newline at end of file diff --git a/mirai-japt/mirai-japt.postfixTemplates b/mirai-japt/mirai-japt.postfixTemplates new file mode 100644 index 000000000..9df0de9fd --- /dev/null +++ b/mirai-japt/mirai-japt.postfixTemplates @@ -0,0 +1,4 @@ +# Contact + +.blocking : 阻塞式包装 + net.mamoe.mirai.contact.QQ [net.mamoe.mirai.japt.BlockingContacts] -> BlockingQQ. BlockingContacts.createBlocking($var$) diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java new file mode 100644 index 000000000..dcc1585cb --- /dev/null +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java @@ -0,0 +1,26 @@ +package net.mamoe.mirai.japt; + +import net.mamoe.mirai.Bot; +import net.mamoe.mirai.message.MessageChain; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public interface BlockingContact { + /** + * 这个联系人所属 [Bot] + */ + @NotNull + Bot getBot(); + + /** + * 可以是 QQ 号码或者群号码 [GroupId]. + */ + long getId(); + + /** + * 向这个对象发送消息. + * <p> + * 速度太快会被服务器屏蔽(无响应). 在测试中不延迟地发送 6 条消息就会被屏蔽之后的数据包 1 秒左右. + */ + void sendMessage(@NotNull MessageChain messages); +} diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContacts.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContacts.java new file mode 100644 index 000000000..de7a3b9cf --- /dev/null +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContacts.java @@ -0,0 +1,22 @@ +package net.mamoe.mirai.japt; + +import net.mamoe.mirai.contact.Group; +import net.mamoe.mirai.contact.Member; +import net.mamoe.mirai.contact.QQ; + +/** + * 构造阻塞式的联系人. + */ +public final class BlockingContacts { + public static BlockingQQ createBlocking(QQ qq) { + return new BlockingQQImpl(qq); + } + + public static BlockingGroup createBlocking(Group group) { + return new BlockingGroupImpl(group); + } + + public static BlockingMember createBlocking(Member member) { + return new BlockingMemberImpl(member); + } +} diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java new file mode 100644 index 000000000..37fdc62b1 --- /dev/null +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java @@ -0,0 +1,57 @@ +package net.mamoe.mirai.japt; + +import net.mamoe.mirai.network.protocol.tim.packet.action.GroupInfo; + +import java.util.Map; + +@SuppressWarnings("unused") +public interface BlockingGroup extends BlockingContact { + /** + * 内部 ID. 内部 ID 为 [GroupId] 的映射 + */ + Long getInternalId(); + + /** + * 群主 (同步事件更新) + * 进行 [updateGroupInfo] 时将会更新这个值. + */ + BlockingMember getOwner(); + + /** + * 群名称 (同步事件更新) + * 进行 [updateGroupInfo] 时将会更新这个值. + */ + String getName(); + + /** + * 入群公告, 没有时为空字符串. (同步事件更新) + * 进行 [updateGroupInfo] 时将会更新这个值. + */ + String getAnnouncement(); + + /** + * 在 [Group] 实例创建的时候查询一次. 并与事件同步事件更新 + * <p> + * **注意**: 获得的列表仅为这一时刻的成员列表的镜像. 它将不会被更新 + */ + Map<Long, BlockingMember> getMembers(); + + /** + * 获取群成员. 若此 ID 的成员不存在, 则会抛出 [kotlin.NoSuchElementException] + */ + BlockingMember getMember(long id); + + /** + * 更新群资料. 群资料会与服务器事件同步事件更新, 一般情况下不需要手动更新. + * + * @return 这一时刻的群资料 + */ + GroupInfo updateGroupInfo(); + + /** + * 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败 + */ + boolean quit(); + + String toFullString(); +} \ No newline at end of file diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingMember.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingMember.java new file mode 100644 index 000000000..bea594a00 --- /dev/null +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingMember.java @@ -0,0 +1,29 @@ +package net.mamoe.mirai.japt; + +import net.mamoe.mirai.contact.MemberPermission; + +@SuppressWarnings("unused") +public interface BlockingMember { + /** + * 所在的群 + */ + BlockingGroup getGroup(); + + /** + * 权限 + */ + MemberPermission getPermission(); + + /** + * 禁言 + * + * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. + * @return 若机器人无权限禁言这个群成员, 返回 `false` + */ + Boolean mute(int durationSeconds); + + /** + * 解除禁言 + */ + void unmute(); +} diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java new file mode 100644 index 000000000..e01aa246c --- /dev/null +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java @@ -0,0 +1,31 @@ +package net.mamoe.mirai.japt; + +import net.mamoe.mirai.contact.data.Profile; +import net.mamoe.mirai.network.protocol.tim.packet.action.FriendNameRemark; +import net.mamoe.mirai.network.protocol.tim.packet.action.PreviousNameList; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public interface BlockingQQ extends BlockingContact { + /** + * 查询用户资料 + */ + @NotNull + Profile queryProfile(); + + /** + * 查询曾用名. + * <p> + * 曾用名可能是: + * - 昵称 + * - 共同群内的群名片 + */ + @NotNull + PreviousNameList queryPreviousNameList(); + + /** + * 查询机器人账号给这个人设置的备注 + */ + @NotNull + FriendNameRemark queryRemark(); +} diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/BlockingContacts.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/BlockingContacts.kt new file mode 100644 index 000000000..e583d33b5 --- /dev/null +++ b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/BlockingContacts.kt @@ -0,0 +1,11 @@ +@file:Suppress("NOTHING_TO_INLINE", "unused") + +package net.mamoe.mirai.japt + +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.QQ + +inline fun Group.blocking(): BlockingGroup = BlockingContacts.createBlocking(this) +inline fun QQ.blocking(): BlockingQQ = BlockingContacts.createBlocking(this) +inline fun Member.blocking(): BlockingMember = BlockingContacts.createBlocking(this) \ No newline at end of file diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/BlockingContactsImpl.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/BlockingContactsImpl.kt new file mode 100644 index 000000000..550d5a807 --- /dev/null +++ b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/BlockingContactsImpl.kt @@ -0,0 +1,46 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + +package net.mamoe.mirai.japt + +import kotlinx.coroutines.runBlocking +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.contact.QQ +import net.mamoe.mirai.contact.data.Profile +import net.mamoe.mirai.message.MessageChain +import net.mamoe.mirai.network.protocol.tim.packet.action.FriendNameRemark +import net.mamoe.mirai.network.protocol.tim.packet.action.GroupInfo +import net.mamoe.mirai.network.protocol.tim.packet.action.PreviousNameList + +internal class BlockingQQImpl(private val delegate: QQ) : BlockingQQ { + override fun getBot(): Bot = delegate.bot + override fun getId(): Long = delegate.id.toLong() + override fun sendMessage(messages: MessageChain) = runBlocking { delegate.sendMessage(messages) } + override fun queryProfile(): Profile = runBlocking { delegate.queryProfile() } + override fun queryPreviousNameList(): PreviousNameList = runBlocking { delegate.queryPreviousNameList() } + override fun queryRemark(): FriendNameRemark = runBlocking { delegate.queryRemark() } +} + +internal class BlockingGroupImpl(private val delegate: Group) : BlockingGroup { + override fun sendMessage(messages: MessageChain) = runBlocking { delegate.sendMessage(messages) } + override fun getOwner(): BlockingMember = delegate.owner.blocking() + override fun getName(): String = delegate.name + override fun getId(): Long = delegate.id.toLong() + override fun updateGroupInfo(): GroupInfo = runBlocking { delegate.updateGroupInfo() } + override fun toFullString(): String = delegate.toFullString() + override fun getMember(id: Long): BlockingMember = delegate.getMember(id.toUInt()).blocking() + override fun getBot(): Bot = delegate.bot + override fun getAnnouncement(): String = delegate.announcement + override fun getMembers(): MutableMap<Long, BlockingMember> = delegate.members.mapKeys { it.key.toLong() }.mapValues { it.value.blocking() }.toMutableMap() + override fun getInternalId(): Long = delegate.internalId.value.toLong() + override fun quit(): Boolean = runBlocking { delegate.quit().isSuccess } +} + +internal class BlockingMemberImpl(private val delegate: Member) : BlockingMember { + override fun getGroup(): BlockingGroup = delegate.group.blocking() + override fun getPermission(): MemberPermission = delegate.permission + override fun mute(durationSeconds: Int): Boolean = runBlocking { delegate.mute(durationSeconds) } + override fun unmute() = runBlocking { delegate.unmute() } +} \ No newline at end of file diff --git a/mirai-japt/src/test/kotlin/BlockingTest.java b/mirai-japt/src/test/kotlin/BlockingTest.java new file mode 100644 index 000000000..385ab7c23 --- /dev/null +++ b/mirai-japt/src/test/kotlin/BlockingTest.java @@ -0,0 +1,14 @@ +public class BlockingTest { + + + public static void main(String[] args) { + //Bot bot = new Bot(new BotAccount(123456, ""), EmptyCoroutineContext.INSTANCE); + //if (bot.getNetwork().login() != LoginResult.) { + // throw IllegalStateException("Login failed") + //} + //bot.getContacts().getGroups(); + + //var qq = BlockingContacts.createBlocking(bot.getContacts().getQQ(123L)) + //println(createBlocking.queryRemark()) + } +} diff --git a/mirai-japt/src/test/kotlin/TestBlocking.kt b/mirai-japt/src/test/kotlin/TestBlocking.kt new file mode 100644 index 000000000..01393988a --- /dev/null +++ b/mirai-japt/src/test/kotlin/TestBlocking.kt @@ -0,0 +1,31 @@ +import net.mamoe.mirai.Bot +import net.mamoe.mirai.BotAccount +import net.mamoe.mirai.japt.BlockingContacts +import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult +import java.io.File + +@ExperimentalUnsignedTypes +private fun readTestAccount(): BotAccount? { + val file = File("testAccount.txt") + if (!file.exists() || !file.canRead()) { + return null + } + + val lines = file.readLines() + return try { + BotAccount(lines[0].toUInt(), lines[1]) + } catch (e: Exception) { + null + } +} + +@ExperimentalUnsignedTypes +suspend fun main() { + val bot = Bot(readTestAccount()!!) + if (bot.network.login() != LoginResult.SUCCESS) { + throw IllegalStateException("Login failed") + } + + val createBlocking = BlockingContacts.createBlocking(bot.contacts.getQQ(123L)) + println(createBlocking.queryRemark()) +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 4deff9a2c..1280fff2e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -36,6 +36,7 @@ include(':mirai-core') include(':mirai-core-timpc') include(':mirai-core-qqandroid') +include(':mirai-japt') include(':mirai-console') //include(':mirai-api') include(':mirai-api-http')