mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-04 07:30:15 +08:00
New event listeners
This commit is contained in:
parent
ffdb7c03fc
commit
126b221079
19
README.md
19
README.md
@ -3,7 +3,7 @@
|
||||
|
||||
一个以<b>TIM QQ协议(非web)</b>驱动的QQ机器人服务端核心
|
||||
采用服务端-插件模式运行,同时提供独立的协议层库
|
||||
**我们承诺项目的所有模块均开源**
|
||||
项目的所有模块均开源
|
||||
|
||||
项目处于开发阶段,学生无法每日大量更新。
|
||||
项目还有很多未完善的地方, 欢迎任何的代码贡献, 或是 issue.
|
||||
@ -22,19 +22,12 @@
|
||||
|
||||
### 事件 Hook
|
||||
#### Java:
|
||||
```
|
||||
MiraiEventHook.onEvent(FriendMessageEvent.class)
|
||||
.handler(a -> {
|
||||
if(a.message.eq("你好"))
|
||||
a.getSender().sendMessage("你好!");
|
||||
})
|
||||
.mountAlways();
|
||||
```
|
||||
暂不支持
|
||||
#### Kotlin:
|
||||
```
|
||||
FriendMessageEvent::class.hookAlways{
|
||||
if(it.message eq "你好")
|
||||
it.reply("你好!")
|
||||
```kotlin
|
||||
FriendMessageEvent.subscribeAlways{
|
||||
if(it.message eq "你好")
|
||||
it.reply("你好!")
|
||||
}
|
||||
```
|
||||

|
||||
|
@ -5,58 +5,59 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* MiraiAPI provides
|
||||
* - the status of the Mirai-Core
|
||||
* - the fundamental bot operations.
|
||||
* - the plugin status.
|
||||
*
|
||||
* It was designed for users, not developers,
|
||||
* Web-based controller, UI controller or console is depending on Mirai-API
|
||||
*
|
||||
* Mirai-API does NOT contains fancy objects, and this means there are less functions it can do compare with Mirai-Core
|
||||
*
|
||||
* Again, for extending/developing Mirai, you should refer to Mirai-Core
|
||||
* for only using , you should refer to Mirai-API
|
||||
* - the status of the Mirai-Core
|
||||
* - the fundamental bot operations.
|
||||
* - the plugin status.
|
||||
* <p>
|
||||
* It was designed for users, not developers,
|
||||
* Web-based controller, UI controller or console is depending on Mirai-API
|
||||
* <p>
|
||||
* Mirai-API does NOT contains fancy objects, and this means there are less functions it can do compare with Mirai-Core
|
||||
* <p>
|
||||
* Again, for extending/developing Mirai, you should refer to Mirai-Core
|
||||
* for only using , you should refer to Mirai-API
|
||||
*/
|
||||
public class MiraiAPI {
|
||||
|
||||
public static void startMirai(String[] args){
|
||||
public static void startMirai(String[] args) {
|
||||
MiraiMain.main(args);
|
||||
}
|
||||
|
||||
public static void closeMirai(){
|
||||
MiraiServer.getInstance().shutdown();
|
||||
public static void closeMirai() {
|
||||
MiraiServer.INSTANCE.shutdown();
|
||||
}
|
||||
|
||||
public static void restartMirai(String[] args){
|
||||
MiraiServer.getInstance().shutdown();
|
||||
public static void restartMirai(String[] args) {
|
||||
MiraiServer.INSTANCE.shutdown();
|
||||
MiraiMain.main(args);
|
||||
}
|
||||
|
||||
public static String getMiraiVersion(){
|
||||
public static String getMiraiVersion() {
|
||||
return MiraiServer.MIRAI_VERSION;
|
||||
}
|
||||
|
||||
public static String getQQPortocolVersion(){
|
||||
public static String getQQPortocolVersion() {
|
||||
return MiraiServer.QQ_VERSION;
|
||||
}
|
||||
|
||||
public static boolean isMiraiEnabled(){
|
||||
return MiraiServer.getInstance()!=null;
|
||||
public static boolean isMiraiEnabled() {
|
||||
// TODO: 2019/10/2
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<String> getEnabledPluginList(){
|
||||
public static List<String> getEnabledPluginList() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public static List<Long> getEnabledBots(){
|
||||
public static List<Long> getEnabledBots() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public static Bot getBot(long qq){
|
||||
public static Bot getBot(long qq) {
|
||||
return new Bot(qq);
|
||||
}
|
||||
|
||||
public static void addBot(long qq, String password){
|
||||
public static void addBot(long qq, String password) {
|
||||
|
||||
}
|
||||
|
||||
|
@ -13,15 +13,23 @@
|
||||
<artifactId>mirai-console</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.mamoe</groupId>
|
||||
<artifactId>mirai-core</artifactId>
|
||||
<version>1.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-test</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@ -37,6 +45,50 @@
|
||||
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>test-compile</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<jvmTarget>1.8</jvmTarget>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>testCompile</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
@ -3,8 +3,6 @@ package net.mamoe.mirai;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
@ -13,7 +11,6 @@ public final class MiraiMain {
|
||||
private static MiraiServer server;
|
||||
|
||||
public static void main(String[] args) {
|
||||
server = new MiraiServer();
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> server.shutdown()));
|
||||
}
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
package net.mamoe.mirai;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.mamoe.mirai.event.MiraiEventManager;
|
||||
import net.mamoe.mirai.event.events.server.ServerDisabledEvent;
|
||||
import net.mamoe.mirai.event.events.server.ServerEnabledEvent;
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState;
|
||||
import net.mamoe.mirai.task.MiraiTaskManager;
|
||||
import net.mamoe.mirai.utils.*;
|
||||
import net.mamoe.mirai.utils.config.MiraiConfig;
|
||||
import net.mamoe.mirai.utils.setting.MiraiSettingListSection;
|
||||
import net.mamoe.mirai.utils.setting.MiraiSettingMapSection;
|
||||
import net.mamoe.mirai.utils.setting.MiraiSettings;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/**
|
||||
* Mirai 服务器.
|
||||
* 管理一些基础的事务
|
||||
*
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public final class MiraiServer {
|
||||
private static MiraiServer instance;
|
||||
|
||||
public static MiraiServer getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public final static String MIRAI_VERSION = "1.0.0";
|
||||
|
||||
public final static String QQ_VERSION = "4.9.0";
|
||||
|
||||
|
||||
@Getter //is running under UNIX
|
||||
private boolean unix;
|
||||
|
||||
@Getter//file pathq
|
||||
public File parentFolder;
|
||||
|
||||
@Getter
|
||||
MiraiEventManager eventManager;
|
||||
@Getter
|
||||
MiraiTaskManager taskManager;
|
||||
|
||||
@Getter
|
||||
MiraiLogger logger;
|
||||
|
||||
MiraiSettings settings;
|
||||
|
||||
MiraiConfig qqs;
|
||||
|
||||
|
||||
MiraiServer() {
|
||||
instance = this;
|
||||
this.onLoaded();
|
||||
this.onEnabled();
|
||||
}
|
||||
|
||||
private boolean enabled;
|
||||
|
||||
void shutdown() {
|
||||
if (this.enabled) {
|
||||
getLogger().info("About to shutdown Mirai");
|
||||
this.eventManager.broadcastEventAsync(new ServerDisabledEvent());
|
||||
getLogger().info("Data have been saved");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void onLoaded() {
|
||||
this.parentFolder = new File(System.getProperty("user.dir"));
|
||||
this.unix = !System.getProperties().getProperty("os.name").toUpperCase().contains("WINDOWS");
|
||||
|
||||
this.logger = MiraiLogger.Companion;
|
||||
this.eventManager = MiraiEventManager.getInstance();
|
||||
this.taskManager = MiraiTaskManager.getInstance();
|
||||
|
||||
getLogger().info("About to run Mirai (" + MiraiServer.MIRAI_VERSION + ") under " + (isUnix() ? "unix" : "windows"));
|
||||
getLogger().info("Loading data under " + LoggerTextFormat.GREEN + this.parentFolder);
|
||||
|
||||
File setting = new File(this.parentFolder + "/Mirai.ini");
|
||||
getLogger().info("Selecting setting from " + LoggerTextFormat.GREEN + setting);
|
||||
|
||||
/*
|
||||
if (!setting.exists()) {
|
||||
this.initSetting(setting);
|
||||
} else {
|
||||
this.settings = new MiraiSettings(setting);
|
||||
}
|
||||
|
||||
File qqs = new File(this.parentFolder + "/QQ.yml");
|
||||
getLogger().info("Reading QQ accounts from " + LoggerTextFormat.GREEN + qqs);
|
||||
if (!qqs.exists()) {
|
||||
this.initQQConfig(qqs);
|
||||
} else {
|
||||
this.qqs = new MiraiConfig(qqs);
|
||||
}
|
||||
if (this.qqs.isEmpty()) {
|
||||
this.initQQConfig(qqs);
|
||||
}*/
|
||||
|
||||
/*
|
||||
MiraiSettingMapSection qqs = this.setting.getMapSection("qq");
|
||||
qqs.forEach((a,p) -> {
|
||||
this.getLogger().info("Finding available ports between " + "1-65536");
|
||||
try {
|
||||
int port = MiraiNetwork.getAvailablePort();
|
||||
this.getLogger().info("Listening on port " + port);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
private void initSetting(File setting) {
|
||||
getLogger().info("Thanks for using Mirai");
|
||||
getLogger().info("initializing Settings");
|
||||
try {
|
||||
if (setting.createNewFile()) {
|
||||
getLogger().info("Mirai Config Created");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.settings = new MiraiSettings(setting);
|
||||
MiraiSettingMapSection network = this.settings.getMapSection("network");
|
||||
network.set("enable_proxy", "not supporting yet");
|
||||
|
||||
MiraiSettingListSection proxy = this.settings.getListSection("proxy");
|
||||
proxy.add("1.2.3.4:95");
|
||||
proxy.add("1.2.3.4:100");
|
||||
|
||||
MiraiSettingMapSection worker = this.settings.getMapSection("worker");
|
||||
worker.set("core_task_pool_worker_amount", 5);
|
||||
|
||||
MiraiSettingMapSection plugin = this.settings.getMapSection("plugin");
|
||||
plugin.set("debug", false);
|
||||
|
||||
this.settings.save();
|
||||
getLogger().info("initialized; changing can be made in setting file: " + setting.toString());
|
||||
}
|
||||
|
||||
private void initQQConfig(File qqConfig) {
|
||||
this.qqs = new MiraiConfig(qqConfig);
|
||||
getLogger().info("QQ account initialized; changing can be made in Config file: " + qqConfig.toString());
|
||||
getLogger().info("QQ 账户管理初始化完毕");
|
||||
}
|
||||
|
||||
private void onEnabled() {
|
||||
this.enabled = true;
|
||||
this.eventManager.broadcastEventAsync(new ServerEnabledEvent());
|
||||
getLogger().info(LoggerTextFormat.GREEN + "Server enabled; Welcome to Mirai");
|
||||
getLogger().info("Mirai Version=" + MiraiServer.MIRAI_VERSION + " QQ Version=" + MiraiServer.QQ_VERSION);
|
||||
|
||||
getLogger().info("Initializing [Bot]s");
|
||||
|
||||
try {
|
||||
getAvailableBot();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
/*
|
||||
this.qqs.keySet().stream().map(key -> this.qqs.getSection(key)).forEach(section -> {
|
||||
getLogger().info("Initializing [Bot] " + section.getString("account"));
|
||||
try {
|
||||
Bot bot = new Bot(section);
|
||||
var state = bot.network.tryLogin$mirai_core().of();
|
||||
//bot.network.tryLogin$mirai_core().whenComplete((state, e) -> {
|
||||
if (state == LoginState.SUCCESS) {
|
||||
Bot.instances.add(bot);
|
||||
getLogger().success(" Login Succeed");
|
||||
} else {
|
||||
getLogger().error(" Login Failed with error " + state);
|
||||
bot.close();
|
||||
}
|
||||
// }).of();
|
||||
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
getLogger().error("Could not load QQ bots config!");
|
||||
System.exit(1);
|
||||
}
|
||||
});*/
|
||||
}
|
||||
|
||||
|
||||
String qqList =
|
||||
"1683921395----bb22222\n";
|
||||
|
||||
private Bot getAvailableBot() throws ExecutionException, InterruptedException {
|
||||
for (String it : qqList.split("\n")) {
|
||||
var strings = it.split("----");
|
||||
var bot = new Bot(new BotAccount(Long.parseLong(strings[0]), strings[1]), new Console());
|
||||
|
||||
if (bot.network.tryLogin(200).get() == LoginState.SUCCESS) {
|
||||
MiraiLoggerKt.success(bot, "Login succeed");
|
||||
return bot;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
200
mirai-console/src/main/java/net/mamoe/mirai/MiraiServer.kt
Normal file
200
mirai-console/src/main/java/net/mamoe/mirai/MiraiServer.kt
Normal file
@ -0,0 +1,200 @@
|
||||
package net.mamoe.mirai
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import lombok.Getter
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
|
||||
import net.mamoe.mirai.task.MiraiTaskManager
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.config.MiraiConfig
|
||||
import net.mamoe.mirai.utils.setting.MiraiSettings
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mirai 服务器.
|
||||
* 管理一些基础的事务
|
||||
*
|
||||
* @author NaturalHG
|
||||
*/
|
||||
object MiraiServer {
|
||||
const val MIRAI_VERSION = "1.0.0"
|
||||
|
||||
const val QQ_VERSION = "4.9.0"
|
||||
|
||||
@Getter //is running under UNIX
|
||||
var isUnix: Boolean = false
|
||||
private set
|
||||
|
||||
@Getter
|
||||
var parentFolder: File = File(System.getProperty("user.dir"))
|
||||
|
||||
@Getter
|
||||
var taskManager: MiraiTaskManager
|
||||
internal set
|
||||
|
||||
@Getter
|
||||
var logger: MiraiLogger
|
||||
internal set
|
||||
|
||||
internal lateinit var settings: MiraiSettings
|
||||
|
||||
internal lateinit var qqs: MiraiConfig
|
||||
|
||||
private var enabled: Boolean = false
|
||||
|
||||
|
||||
init {
|
||||
this.isUnix = !System.getProperties().getProperty("os.name").toUpperCase().contains("WINDOWS")
|
||||
|
||||
this.logger = MiraiLogger
|
||||
this.taskManager = MiraiTaskManager.getInstance()
|
||||
|
||||
logger.info("About to run Mirai (" + MiraiServer.MIRAI_VERSION + ") under " + if (isUnix) "unix" else "windows")
|
||||
logger.info("Loading data under " + LoggerTextFormat.GREEN + this.parentFolder)
|
||||
|
||||
val setting = this.parentFolder + "/Mirai.ini"
|
||||
logger.info("Selecting setting from " + LoggerTextFormat.GREEN + setting)
|
||||
|
||||
/*
|
||||
if (!setting.exists()) {
|
||||
this.initSetting(setting);
|
||||
} else {
|
||||
this.settings = new MiraiSettings(setting);
|
||||
}
|
||||
|
||||
File qqs = new File(this.parentFolder + "/QQ.yml");
|
||||
getLogger().info("Reading QQ accounts from " + LoggerTextFormat.GREEN + qqs);
|
||||
if (!qqs.exists()) {
|
||||
this.initQQConfig(qqs);
|
||||
} else {
|
||||
this.qqs = new MiraiConfig(qqs);
|
||||
}
|
||||
if (this.qqs.isEmpty()) {
|
||||
this.initQQConfig(qqs);
|
||||
}*/
|
||||
|
||||
/*
|
||||
MiraiSettingMapSection qqs = this.setting.getMapSection("qq");
|
||||
qqs.forEach((a,p) -> {
|
||||
this.getLogger().info("Finding available ports between " + "1-65536");
|
||||
try {
|
||||
int port = MiraiNetwork.getAvailablePort();
|
||||
this.getLogger().info("Listening on port " + port);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
|
||||
this.reload()
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
if (this.enabled) {
|
||||
logger.info("About to shutdown Mirai")
|
||||
logger.info("Data have been saved")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun initSetting(setting: File) {
|
||||
logger.info("Thanks for using Mirai")
|
||||
logger.info("initializing Settings")
|
||||
try {
|
||||
if (setting.createNewFile()) {
|
||||
logger.info("Mirai Config Created")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
this.settings = MiraiSettings(setting)
|
||||
val network = this.settings.getMapSection("network")
|
||||
network.set("enable_proxy", "not supporting yet")
|
||||
|
||||
val proxy = this.settings.getListSection("proxy")
|
||||
proxy.add("1.2.3.4:95")
|
||||
proxy.add("1.2.3.4:100")
|
||||
|
||||
val worker = this.settings.getMapSection("worker")
|
||||
worker.set("core_task_pool_worker_amount", 5)
|
||||
|
||||
val plugin = this.settings.getMapSection("plugin")
|
||||
plugin.set("debug", false)
|
||||
|
||||
this.settings.save()
|
||||
logger.info("initialized; changing can be made in setting file: $setting")
|
||||
}
|
||||
|
||||
private fun initQQConfig(qqConfig: File) {
|
||||
this.qqs = MiraiConfig(qqConfig)
|
||||
MiraiLogger.info("QQ account initialized; changing can be made in Config file: $qqConfig")
|
||||
logger.info("QQ 账户管理初始化完毕")
|
||||
}
|
||||
|
||||
private fun reload() {
|
||||
this.enabled = true
|
||||
MiraiLogger.info(LoggerTextFormat.GREEN.toString() + "Server enabled; Welcome to Mirai")
|
||||
MiraiLogger.info("Mirai Version=" + MiraiServer.MIRAI_VERSION + " QQ Version=" + MiraiServer.QQ_VERSION)
|
||||
|
||||
MiraiLogger.info("Initializing [Bot]s")
|
||||
|
||||
try {
|
||||
availableBot
|
||||
} catch (e: ExecutionException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: InterruptedException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
/*
|
||||
this.qqs.keySet().stream().map(key -> this.qqs.getSection(key)).forEach(section -> {
|
||||
getLogger().info("Initializing [Bot] " + section.getString("account"));
|
||||
try {
|
||||
Bot bot = new Bot(section);
|
||||
var state = bot.network.tryLogin$mirai_core().of();
|
||||
//bot.network.tryLogin$mirai_core().whenComplete((state, e) -> {
|
||||
if (state == LoginState.SUCCESS) {
|
||||
Bot.instances.add(bot);
|
||||
getLogger().green(" Login Succeed");
|
||||
} else {
|
||||
getLogger().error(" Login Failed with error " + state);
|
||||
bot.close();
|
||||
}
|
||||
// }).of();
|
||||
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
getLogger().error("Could not load QQ bots config!");
|
||||
System.exit(1);
|
||||
}
|
||||
});*/
|
||||
}
|
||||
|
||||
|
||||
//todo only for test now
|
||||
private var qqList = "1683921395----bb22222\n"
|
||||
|
||||
private val availableBot: Bot
|
||||
@Throws(ExecutionException::class, InterruptedException::class)
|
||||
get() {
|
||||
for (it in qqList.split("\n").dropLastWhile { it.isEmpty() }.toTypedArray()) {
|
||||
val strings = it.split("----").dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val bot = Bot(BotAccount(strings[0].toLong(), strings[1]), Console())
|
||||
|
||||
if (runBlocking { bot.network.tryLogin(200).await() } === LoginState.SUCCESS) {
|
||||
bot.green("Login succeed")
|
||||
return bot
|
||||
}
|
||||
}
|
||||
|
||||
throw RuntimeException()
|
||||
}
|
||||
}
|
@ -106,6 +106,11 @@
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<args>
|
||||
<arg>-XXLanguage:+InlineClasses</arg>
|
||||
</args>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
@ -1,123 +0,0 @@
|
||||
package net.mamoe.mirai;
|
||||
|
||||
import net.mamoe.mirai.contact.Group;
|
||||
import net.mamoe.mirai.contact.QQ;
|
||||
import net.mamoe.mirai.network.BotNetworkHandler;
|
||||
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler;
|
||||
import net.mamoe.mirai.utils.BotAccount;
|
||||
import net.mamoe.mirai.utils.ContactList;
|
||||
import net.mamoe.mirai.utils.MiraiLogger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
|
||||
* Mirai 为多账号设计, 可同时维护多个机器人.
|
||||
* <br>
|
||||
* {@link Bot} 由 3 个模块组成.
|
||||
* {@linkplain ContactSystem 联系人管理}: 可通过 {@link Bot#contacts} 访问
|
||||
* {@linkplain TIMBotNetworkHandler 网络处理器}: 可通过 {@link Bot#network} 访问
|
||||
* {@linkplain BotAccount 机器人账号信息}: 可通过 {@link Bot#account} 访问
|
||||
* <br>
|
||||
* 若你需要得到机器人的 QQ 账号, 请访问 {@link Bot#account}
|
||||
* 若你需要得到服务器上所有机器人列表, 请访问 {@link Bot#instances}
|
||||
*
|
||||
* <p>
|
||||
* Bot that is the base of the whole program.
|
||||
* It consists of
|
||||
* a {@link ContactSystem}, which manage contacts such as {@link QQ} and {@link Group};
|
||||
* a {@link TIMBotNetworkHandler}, which manages the connection to the server;
|
||||
* a {@link BotAccount}, which stores the account information(e.g. qq number the bot)
|
||||
* <br>
|
||||
* To of all the QQ contacts, access {@link Bot#account}
|
||||
* To of all the Robot instance, access {@link Bot#instances}
|
||||
* </p>
|
||||
*
|
||||
* @author Him188moe
|
||||
* @author NatrualHG
|
||||
* @see net.mamoe.mirai.contact.Contact
|
||||
*/
|
||||
public final class Bot implements Closeable {
|
||||
public static final List<Bot> instances = Collections.synchronizedList(new LinkedList<>());
|
||||
|
||||
{
|
||||
instances.add(this);
|
||||
}
|
||||
|
||||
public final int id = _id.getAndAdd(1);
|
||||
|
||||
public final BotAccount account;
|
||||
|
||||
public final ContactSystem contacts = new ContactSystem();
|
||||
|
||||
public final BotNetworkHandler network;
|
||||
|
||||
public final MiraiLogger logger;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Bot{id=%d,qq=%d}", id, this.account.getQqNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
* Bot 联系人管理.
|
||||
*
|
||||
* @see Bot#contacts
|
||||
*/
|
||||
public final class ContactSystem {
|
||||
private final ContactList<Group> groups = new ContactList<>();
|
||||
private final ContactList<QQ> qqs = new ContactList<>();
|
||||
|
||||
private ContactSystem() {
|
||||
|
||||
}
|
||||
|
||||
public QQ getQQ(long qqNumber) {
|
||||
if (!this.qqs.containsKey(qqNumber)) {
|
||||
this.qqs.put(qqNumber, new QQ(Bot.this, qqNumber));
|
||||
}
|
||||
return this.qqs.get(qqNumber);
|
||||
}
|
||||
|
||||
public Group getGroupByNumber(long groupNumber) {
|
||||
if (!this.groups.containsKey(groupNumber)) {
|
||||
this.groups.put(groupNumber, new Group(Bot.this, groupNumber));
|
||||
}
|
||||
return groups.get(groupNumber);
|
||||
}
|
||||
|
||||
public Group getGroupById(long groupId) {
|
||||
return getGroupByNumber(Group.Companion.groupIdToNumber(groupId));
|
||||
}
|
||||
}
|
||||
|
||||
public Bot(@NotNull BotAccount account, @NotNull MiraiLogger logger) {
|
||||
Objects.requireNonNull(account);
|
||||
|
||||
this.account = account;
|
||||
|
||||
this.logger = Objects.requireNonNull(logger);
|
||||
this.logger.setIdentity("Bot" + this.id + "(" + this.account.getQqNumber() + ")");
|
||||
|
||||
this.network = new TIMBotNetworkHandler(this);
|
||||
}
|
||||
|
||||
|
||||
public void close() {
|
||||
this.network.close();
|
||||
this.contacts.groups.values().forEach(Group::close);
|
||||
this.contacts.groups.clear();
|
||||
this.contacts.qqs.clear();
|
||||
}
|
||||
|
||||
/* PRIVATE */
|
||||
|
||||
private static final AtomicInteger _id = new AtomicInteger(0);
|
||||
}
|
||||
|
103
mirai-core/src/main/java/net/mamoe/mirai/Bot.kt
Normal file
103
mirai-core/src/main/java/net/mamoe/mirai/Bot.kt
Normal file
@ -0,0 +1,103 @@
|
||||
package net.mamoe.mirai
|
||||
|
||||
import net.mamoe.mirai.Bot.ContactSystem
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
|
||||
import net.mamoe.mirai.utils.BotAccount
|
||||
import net.mamoe.mirai.utils.ContactList
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.io.Closeable
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
|
||||
* Mirai 为多账号设计, 可同时维护多个机器人.
|
||||
* <br></br>
|
||||
* [Bot] 由 3 个模块组成.
|
||||
* [联系人管理][ContactSystem]: 可通过 [Bot.contacts] 访问
|
||||
* [网络处理器][TIMBotNetworkHandler]: 可通过 [Bot.network] 访问
|
||||
* [机器人账号信息][BotAccount]: 可通过 [Bot.account] 访问
|
||||
* <br></br>
|
||||
* 若你需要得到机器人的 QQ 账号, 请访问 [Bot.account]
|
||||
* 若你需要得到服务器上所有机器人列表, 请访问 [Bot.instances]
|
||||
*
|
||||
*
|
||||
*
|
||||
* Bot that is the base of the whole program.
|
||||
* It consists of
|
||||
* a [ContactSystem], which manage contacts such as [QQ] and [Group];
|
||||
* a [TIMBotNetworkHandler], which manages the connection to the server;
|
||||
* a [BotAccount], which stores the account information(e.g. qq number the bot)
|
||||
* <br></br>
|
||||
* To of all the QQ contacts, access [Bot.account]
|
||||
* To of all the Robot instance, access [Bot.instances]
|
||||
*
|
||||
*
|
||||
* @author Him188moe
|
||||
* @author NatrualHG
|
||||
* @see net.mamoe.mirai.contact.Contact
|
||||
*/
|
||||
class Bot(val account: BotAccount, val logger: MiraiLogger) : Closeable {
|
||||
|
||||
val id = createdBotsCount.getAndAdd(1)
|
||||
|
||||
val contacts = ContactSystem()
|
||||
|
||||
val network: BotNetworkHandler = TIMBotNetworkHandler(this)
|
||||
|
||||
init {
|
||||
instances.add(this)
|
||||
|
||||
this.logger.identity = "Bot" + this.id + "(" + this.account.qqNumber + ")"
|
||||
}
|
||||
|
||||
override fun toString(): String = "Bot{id=$id,qq=${account.qqNumber}}"
|
||||
|
||||
/**
|
||||
* Bot 联系人管理.
|
||||
*
|
||||
* @see Bot.contacts
|
||||
*/
|
||||
inner class ContactSystem internal constructor() {
|
||||
val groups = ContactList<Group>()
|
||||
val qqs = ContactList<QQ>()
|
||||
|
||||
fun getQQ(qqNumber: Long): QQ {
|
||||
synchronized(this.qqs) {
|
||||
if (!this.qqs.containsKey(qqNumber)) {
|
||||
this.qqs[qqNumber] = QQ(this@Bot, qqNumber)
|
||||
}
|
||||
return this.qqs[qqNumber]!!
|
||||
}
|
||||
}
|
||||
|
||||
fun getGroupByNumber(groupNumber: Long): Group {
|
||||
synchronized(this.groups) {
|
||||
if (!this.groups.containsKey(groupNumber)) {
|
||||
this.groups[groupNumber] = Group(this@Bot, groupNumber)
|
||||
}
|
||||
return this.groups[groupNumber]!!
|
||||
}
|
||||
}
|
||||
|
||||
fun getGroupById(groupId: Long): Group {
|
||||
return getGroupByNumber(Group.groupIdToNumber(groupId))
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
this.network.close()
|
||||
this.contacts.groups.values.forEach { it.close() }
|
||||
this.contacts.groups.clear()
|
||||
this.contacts.qqs.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val instances: MutableList<Bot> = Collections.synchronizedList(LinkedList())
|
||||
|
||||
private val createdBotsCount = AtomicInteger(0)
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package net.mamoe.mirai
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
|
||||
import net.mamoe.mirai.utils.ContactList
|
||||
|
||||
/**
|
||||
* The mirror of functions in inner classes of [Bot]
|
||||
@ -17,6 +18,10 @@ fun Bot.getGroupByNumber(number: Long): Group = this.contacts.getGroupByNumber(n
|
||||
|
||||
fun Bot.getGroupById(number: Long): Group = this.contacts.getGroupById(number)
|
||||
|
||||
val Bot.groups: ContactList<Group> get() = this.contacts.groups
|
||||
|
||||
val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs
|
||||
|
||||
|
||||
//NetworkHandler
|
||||
suspend fun Bot.sendPacket(packet: ClientPacket) {
|
||||
|
@ -18,6 +18,7 @@ import java.util.concurrent.CompletableFuture
|
||||
* @author Him188moe
|
||||
*/
|
||||
abstract class Contact internal constructor(val bot: Bot, val number: Long) {
|
||||
|
||||
abstract suspend fun sendMessage(message: MessageChain)
|
||||
|
||||
/**
|
||||
|
@ -23,7 +23,7 @@ import java.io.Closeable
|
||||
*/
|
||||
class Group(bot: Bot, number: Long) : Contact(bot, number), Closeable {
|
||||
val groupId = groupNumberToId(number)
|
||||
val members = ContactList<QQ>()
|
||||
val members = ContactList<QQ>()//todo members
|
||||
|
||||
override suspend fun sendMessage(message: MessageChain) {
|
||||
bot.network.message.sendGroupMessage(this, message)
|
||||
|
@ -1,27 +0,0 @@
|
||||
package net.mamoe.mirai.event;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 实现这个接口的事件可以被异步执行或阻塞执行
|
||||
*
|
||||
* @author Him188moe
|
||||
* @see AsyncEventKt 若你使用 kotlin, 请查看针对 kotlin 的优化实现
|
||||
*/
|
||||
public interface AsyncEvent {
|
||||
|
||||
default CompletableFuture<? extends AsyncEvent> broadcastAsync() {
|
||||
return MiraiEventManager.getInstance().broadcastEventAsync(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default <E extends AsyncEvent> CompletableFuture<E> broadcastEventAsync(Consumer<E> callback) {
|
||||
return MiraiEventManager.getInstance().broadcastEventAsync((E) this, callback);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default <E extends AsyncEvent> CompletableFuture<E> broadcastEventAsync(Runnable callback) {
|
||||
return MiraiEventManager.getInstance().broadcastEventAsync((E) this, callback);
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
@file:JvmName("AsyncEventKt")
|
||||
|
||||
package net.mamoe.mirai.event
|
||||
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.function.Consumer
|
||||
|
||||
fun <E : AsyncEvent> E.broadcastAsync(callback: Consumer<E>): CompletableFuture<E> {
|
||||
return MiraiEventManager.getInstance().broadcastEventAsync(this, callback)
|
||||
}
|
||||
|
||||
fun <E : AsyncEvent> E.broadcastAsync(callback: Runnable): CompletableFuture<E> {
|
||||
return MiraiEventManager.getInstance().broadcastEventAsync(this, callback)
|
||||
}
|
||||
|
||||
fun <E : AsyncEvent> E.broadcastAsyncSmart(): CompletableFuture<E> {
|
||||
return MiraiEventManager.getInstance().broadcastEventAsync(this)
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package net.mamoe.mirai.event;
|
||||
|
||||
/**
|
||||
* @author NaturalHG
|
||||
*/
|
||||
public interface Cancellable {
|
||||
boolean isCancelled();
|
||||
|
||||
void cancel(boolean forceCancel);
|
||||
|
||||
void cancel();
|
||||
}
|
53
mirai-core/src/main/java/net/mamoe/mirai/event/Event.kt
Normal file
53
mirai-core/src/main/java/net/mamoe/mirai/event/Event.kt
Normal file
@ -0,0 +1,53 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.event
|
||||
|
||||
import net.mamoe.mirai.event.internal.broadcastInternal
|
||||
|
||||
/**
|
||||
* 所有事件的基类.
|
||||
* 若监听这个类, 监听器将会接收所有事件的广播.
|
||||
*
|
||||
* @see [broadcast] 广播事件
|
||||
* @see [subscribe] 监听事件
|
||||
* @author Him188moe
|
||||
*/
|
||||
abstract class Event {
|
||||
|
||||
/**
|
||||
* 事件是否已取消. 事件需实现 [Cancellable] 才可以被取消, 否则这个字段为常量值 false
|
||||
*/
|
||||
var cancelled: Boolean = false
|
||||
get() = field.takeIf { this is Cancellable } ?: false
|
||||
private set(value) = if (this is Cancellable) {
|
||||
check(!field); field = value
|
||||
} else throw UnsupportedOperationException()
|
||||
|
||||
/**
|
||||
* 取消事件. 事件需实现 [Cancellable] 才可以被取消, 否则调用这个方法将会得到 [UnsupportedOperationException]
|
||||
*
|
||||
* @throws UnsupportedOperationException 如果事件没有实现 [Cancellable]
|
||||
*/
|
||||
@Throws(UnsupportedOperationException::class)
|
||||
fun cancel() {
|
||||
cancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现这个接口的事件可以被取消.
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
interface Cancellable {
|
||||
val cancelled: Boolean
|
||||
|
||||
fun cancel()
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播一个事件的唯一途径
|
||||
*/
|
||||
@Synchronized
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
suspend fun <E : Event> E.broadcast(): E = this.broadcastInternal()
|
@ -1,35 +0,0 @@
|
||||
package net.mamoe.mirai.event;
|
||||
|
||||
import net.mamoe.mirai.utils.EventException;
|
||||
|
||||
/**
|
||||
* @author NatrualHG
|
||||
* @see AsyncEvent
|
||||
*/
|
||||
public abstract class MiraiEvent {
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public boolean isCancelled() {
|
||||
if (!(this instanceof Cancellable)) {
|
||||
return false;
|
||||
}
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
cancel(true);
|
||||
}
|
||||
|
||||
public void cancel(boolean value) {
|
||||
if (!(this instanceof Cancellable)) {
|
||||
throw new EventException("Event is not Cancellable");
|
||||
}
|
||||
this.cancelled = value;
|
||||
}
|
||||
|
||||
public final MiraiEvent broadcast() {
|
||||
MiraiEventManager.getInstance().broadcastEvent(this);
|
||||
return this;
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
package net.mamoe.mirai.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author NatrualHG
|
||||
*/
|
||||
public class MiraiEventHook<T extends MiraiEvent> implements Closeable {
|
||||
|
||||
@Getter
|
||||
Class<T> eventClass;
|
||||
|
||||
@Getter
|
||||
protected volatile Consumer<T> handler;
|
||||
|
||||
@Getter
|
||||
private volatile int priority = 0;
|
||||
|
||||
@Getter
|
||||
private volatile boolean ignoreCancelled = true;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private volatile boolean mount = false;
|
||||
|
||||
/**
|
||||
* return true -> this hook need to be removed
|
||||
*/
|
||||
@Getter
|
||||
protected Predicate<T> validChecker;
|
||||
|
||||
public MiraiEventHook(Class<T> eventClass) {
|
||||
this(eventClass,null);
|
||||
}
|
||||
|
||||
public MiraiEventHook(Class<T> eventClass, Consumer<T> handler){
|
||||
this.eventClass = eventClass;
|
||||
this.handler(handler);
|
||||
}
|
||||
|
||||
public MiraiEventHook<T> handler(Consumer<T> handler) {
|
||||
this.handler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MiraiEventHook<T> priority(int priority) {
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MiraiEventHook<T> ignoreCancelled(boolean ignoreCancelled) {
|
||||
this.ignoreCancelled = ignoreCancelled;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
private MiraiEventHook<T> setValidChecker(Predicate<T> validChecker) {
|
||||
this.validChecker = validChecker;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MiraiEventHook<T> setValidUntil(Predicate<T> valid) {
|
||||
return this.setValidChecker(valid);
|
||||
}
|
||||
|
||||
public MiraiEventHook<T> setValidWhile(Predicate<T> valid) {
|
||||
return this.setValidChecker(valid.negate());
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean accept(MiraiEvent event) {
|
||||
if(!(event instanceof Cancellable && event.isCancelled() && this.isIgnoreCancelled())){
|
||||
this.getHandler().accept((T) event);
|
||||
}
|
||||
return this.validChecker == null || this.validChecker.test((T) event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更加安全高效的方式
|
||||
* Remember to use {@link this.mount()} at last
|
||||
* */
|
||||
|
||||
public static <D extends MiraiEvent> MiraiEventHook<D> onEvent(Class<D> event){
|
||||
return new MiraiEventHook<>(event);
|
||||
}
|
||||
|
||||
public void mount(){
|
||||
if(this.handler == null)this.handler = a -> {};
|
||||
MiraiEventManager.getInstance().registerHook(this);
|
||||
}
|
||||
|
||||
public void mountAlways(){
|
||||
if(this.handler == null)this.handler = a -> {};
|
||||
MiraiEventManager.getInstance().hookAlways(this);
|
||||
}
|
||||
|
||||
public void mountOnce(){
|
||||
if(this.handler == null)this.handler = a -> {};
|
||||
MiraiEventManager.getInstance().hookOnce(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close(){
|
||||
this.handler = null;
|
||||
this.validChecker = null;
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package net.mamoe.mirai.event
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Predicate
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
class MiraiEventHookKt<E : MiraiEvent>(eventClass: Class<E>) : MiraiEventHook<E>(eventClass) {
|
||||
fun onEvent(handler: suspend (E) -> Unit) {
|
||||
this@MiraiEventHookKt.handler = Consumer {
|
||||
runBlocking {
|
||||
handler(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun validChecker(predicate: suspend (E) -> Boolean) {//todo 把 mirai event 变为 suspend, 而不是在这里 run blocking
|
||||
this@MiraiEventHookKt.validChecker = Predicate {
|
||||
runBlocking {
|
||||
predicate(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Kotlin 风格回调
|
||||
* 你的代码可以这样(并且 validChecker 是可选的):
|
||||
*
|
||||
* event.hook {
|
||||
* onEvent {}
|
||||
* validChecker {}
|
||||
* }
|
||||
*/
|
||||
fun <E : MiraiEvent> E.hook(handler: MiraiEventHookKt<E>.() -> Unit): MiraiEventHookKt<E> {
|
||||
return MiraiEventHookKt(this.javaClass).apply(handler)
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
@file:JvmName("MiraiEventKt")
|
||||
|
||||
package net.mamoe.mirai.event
|
||||
|
||||
fun <E : MiraiEvent> E.broadcastSmart(): E {
|
||||
MiraiEventManager.getInstance().broadcastEvent(this as MiraiEvent)
|
||||
return this
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
package net.mamoe.mirai.event;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 线程安全的事件管理器.
|
||||
*
|
||||
* @author NaturalHG
|
||||
* @see MiraiEventManagerKt 若你使用 kotlin, 请查看针对 kotlin 的优化实现
|
||||
*/
|
||||
public class MiraiEventManager {
|
||||
MiraiEventManager() {
|
||||
|
||||
}
|
||||
|
||||
public static MiraiEventManager getInstance() {
|
||||
return EventManager.INSTANCE;//实例来自 kotlin 的 singleton
|
||||
}
|
||||
|
||||
private final ReentrantReadWriteLock hooksLock = new ReentrantReadWriteLock();
|
||||
private Map<Class<? extends MiraiEvent>, List<MiraiEventHook<? extends MiraiEvent>>> hooks = new HashMap<>();
|
||||
|
||||
public <D extends MiraiEvent> void hookUntil(MiraiEventHook<D> hook, Predicate<D> toRemove) {
|
||||
this.mountHook(hook.setValidUntil(toRemove));
|
||||
}
|
||||
|
||||
public <D extends MiraiEvent> void hookWhile(MiraiEventHook<D> hook, Predicate<D> toKeep) {
|
||||
this.mountHook(hook.setValidWhile(toKeep));
|
||||
}
|
||||
|
||||
public <D extends MiraiEvent> void hookAlways(MiraiEventHook<D> hook) {
|
||||
this.hookUntil(hook, (a) -> false);
|
||||
}
|
||||
|
||||
public <D extends MiraiEvent> void hookOnce(MiraiEventHook<D> hook) {
|
||||
this.hookUntil(hook, (a) -> true);
|
||||
}
|
||||
|
||||
public <D extends MiraiEvent> void registerHook(MiraiEventHook<D> hook) {
|
||||
this.mountHook(hook);
|
||||
}
|
||||
|
||||
private <D extends MiraiEvent> void mountHook(MiraiEventHook<D> hook) {
|
||||
if (!hook.isMount()) {
|
||||
hook.setMount(true);
|
||||
hooksLock.writeLock().lock();
|
||||
try {
|
||||
if (!hooks.containsKey(hook.getEventClass())) {
|
||||
hooks.put(hook.getEventClass(), new LinkedList<>() {{
|
||||
add(hook);
|
||||
}});
|
||||
} else {
|
||||
hooks.get(hook.getEventClass()).add(hook);
|
||||
}
|
||||
} finally {
|
||||
hooksLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 不推荐onEvent
|
||||
* 由于不能保证Hook的原子性 非线程安全
|
||||
* 不能保证下一个 D event发生时handler就位
|
||||
*
|
||||
* @author NaturalHG Aug27
|
||||
* use {@link MiraiEventHook::onEvent()} to replace
|
||||
*/
|
||||
|
||||
@Deprecated
|
||||
public <D extends MiraiEvent> MiraiEventHook<D> onEvent(Class<D> event) {
|
||||
MiraiEventHook<D> hook = new MiraiEventHook<>(event);
|
||||
this.registerHook(hook);
|
||||
return hook;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public <D extends MiraiEvent> MiraiEventHook<D> onEventOnce(Class<D> event) {
|
||||
MiraiEventHook<D> hook = new MiraiEventHook<>(event);
|
||||
this.hookOnce(hook);
|
||||
return hook;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public <D extends MiraiEvent> MiraiEventHook<D> onEventUntil(Class<D> event, Predicate<D> toRemove) {
|
||||
MiraiEventHook<D> hook = new MiraiEventHook<>(event);
|
||||
this.hookUntil(hook, toRemove);
|
||||
return hook;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public <D extends MiraiEvent> MiraiEventHook<D> onEventWhile(Class<D> event, Predicate<D> toKeep) {
|
||||
MiraiEventHook<D> hook = new MiraiEventHook<>(event);
|
||||
this.hookWhile(hook, toKeep);
|
||||
return hook;
|
||||
}
|
||||
|
||||
|
||||
public void broadcastEvent(MiraiEvent event) {
|
||||
hooksLock.readLock().lock();
|
||||
try {
|
||||
if (hooks.containsKey(event.getClass())) {
|
||||
hooks.put(event.getClass(),
|
||||
hooks.get(event.getClass())
|
||||
.stream()
|
||||
.sorted(Comparator.comparingInt(MiraiEventHook::getPriority))
|
||||
.filter(a -> !a.accept(event))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
hooksLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public <E extends AsyncEvent> CompletableFuture<E> broadcastEventAsync(E event) {
|
||||
Objects.requireNonNull(event);
|
||||
if (!(event instanceof MiraiEvent)) {
|
||||
throw new IllegalArgumentException("event must be instanceof MiraiEvent");
|
||||
}
|
||||
|
||||
CompletableFuture<E> future = new CompletableFuture<>();
|
||||
future.completeAsync(() -> {
|
||||
MiraiEventManager.this.broadcastEvent((MiraiEvent) event);
|
||||
return event;
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
public <E extends AsyncEvent> CompletableFuture<E> broadcastEventAsync(E event, Consumer<E> callback) {
|
||||
Objects.requireNonNull(event);
|
||||
Objects.requireNonNull(callback);
|
||||
if (!(event instanceof MiraiEvent)) {
|
||||
throw new IllegalArgumentException("event must be instanceof MiraiEvent");
|
||||
}
|
||||
|
||||
CompletableFuture<E> future = new CompletableFuture<>();
|
||||
future.whenComplete((a, b) -> callback.accept(event));
|
||||
future.completeAsync(() -> {
|
||||
MiraiEventManager.this.broadcastEvent((MiraiEvent) event);
|
||||
return event;
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
|
||||
public <D extends AsyncEvent> CompletableFuture<D> broadcastEventAsync(D event, Runnable callback) {
|
||||
return broadcastEventAsync(event, t -> callback.run());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,82 +0,0 @@
|
||||
@file:JvmName("MiraiEventManagerKt")
|
||||
|
||||
package net.mamoe.mirai.event
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.mamoe.mirai.Bot
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* [MiraiEventManager] 的 kotlin 简易化实现.
|
||||
* 若要 hook 一个事件, 你可以:
|
||||
* FriendMessageEvent::class.hookOnce {}
|
||||
* FriendMessageEvent::class.hookAlways {}
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
|
||||
object EventManager : MiraiEventManager()
|
||||
|
||||
/**
|
||||
* 每次事件触发时都会调用 hook
|
||||
*/
|
||||
fun <C : Class<E>, E : MiraiEvent> C.hookAlways(bot: Bot? = null, hook: suspend (E) -> Unit) {
|
||||
MiraiEventManager.getInstance().hookAlways(MiraiEventHook<E>(this) {
|
||||
runBlocking {
|
||||
hook(it)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 当下一次事件触发时调用 hook
|
||||
*/
|
||||
fun <C : Class<E>, E : MiraiEvent> C.hookOnce(bot: Bot? = null, hook: suspend (E) -> Unit) {
|
||||
//todo bot 限制 实现. 或重写 hook
|
||||
MiraiEventManager.getInstance().hookOnce(MiraiEventHook<E>(this) {
|
||||
runBlocking {
|
||||
hook(it)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次事件触发时都会调用 hook, 直到 hook 返回 false 时停止 hook
|
||||
*/
|
||||
fun <C : Class<E>, E : MiraiEvent> C.hookWhile(bot: Bot? = null, hook: suspend (E) -> Boolean) {
|
||||
MiraiEventManager.getInstance().hookAlways(MiraiEventHookSimple(this, hook))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 每次事件触发时都会调用 hook
|
||||
*
|
||||
* @param bot 指定仅限于某个 [Bot] 的事件.
|
||||
*/
|
||||
fun <C : KClass<E>, E : MiraiEvent> C.hookAlways(bot: Bot? = null, hook: suspend (E) -> Unit) {
|
||||
this.java.hookAlways(bot, hook)
|
||||
}
|
||||
|
||||
/**
|
||||
* 当下一次事件触发时调用 hook
|
||||
*/
|
||||
fun <C : KClass<E>, E : MiraiEvent> C.hookOnce(bot: Bot? = null, hook: suspend (E) -> Unit) {
|
||||
this.java.hookOnce(bot, hook)
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次事件触发时都会调用 hook, 直到 hook 返回 false 时停止 hook
|
||||
*/
|
||||
fun <C : KClass<E>, E : MiraiEvent> C.hookWhile(bot: Bot? = null, hook: suspend (E) -> Boolean) {
|
||||
this.java.hookWhile(bot, hook)
|
||||
}
|
||||
|
||||
|
||||
private class MiraiEventHookSimple<E : MiraiEvent>(clazz: Class<E>, val hook: suspend (E) -> Boolean) : MiraiEventHook<E>(clazz) {
|
||||
override fun accept(event: MiraiEvent?): Boolean {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return runBlocking {
|
||||
return@runBlocking !hook.invoke(event as E)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package net.mamoe.mirai.event
|
||||
|
||||
import net.mamoe.mirai.event.internal.Handler
|
||||
import net.mamoe.mirai.event.internal.listeners
|
||||
import net.mamoe.mirai.event.internal.subscribeInternal
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
enum class ListeningStatus {
|
||||
LISTENING,
|
||||
STOPPED
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun <E : Event> KClass<E>.subscribe(handler: suspend (E) -> ListeningStatus) = this.listeners.add(Handler(handler))
|
||||
|
||||
fun <E : Event> KClass<E>.subscribeAlways(listener: suspend (E) -> Unit) = this.subscribeInternal(Handler { listener(it); ListeningStatus.LISTENING })
|
||||
|
||||
fun <E : Event> KClass<E>.subscribeOnce(listener: suspend (E) -> Unit) = this.subscribeInternal(Handler { listener(it); ListeningStatus.STOPPED })
|
||||
|
||||
fun <E : Event, T> KClass<E>.subscribeUntil(valueIfStop: T, block: suspend (E) -> T) = subscribeInternal(Handler { if (block(it) === valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
|
||||
fun <E : Event> KClass<E>.subscribeUntilFalse(block: suspend (E) -> Boolean) = subscribeUntil(false, block)
|
||||
fun <E : Event> KClass<E>.subscribeUntilTrue(block: suspend (E) -> Boolean) = subscribeUntil(true, block)
|
||||
fun <E : Event> KClass<E>.subscribeUntilNull(block: suspend (E) -> Any?) = subscribeUntil(null, block)
|
||||
|
||||
|
||||
fun <E : Event, T> KClass<E>.subscribeWhile(valueIfContinue: T, block: suspend (E) -> T) = subscribeInternal(Handler { if (block(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
|
||||
fun <E : Event> KClass<E>.subscribeWhileFalse(block: suspend (E) -> Boolean) = subscribeWhile(false, block)
|
||||
fun <E : Event> KClass<E>.subscribeWhileTrue(block: suspend (E) -> Boolean) = subscribeWhile(true, block)
|
||||
fun <E : Event> KClass<E>.subscribeWhileNull(block: suspend (E) -> Any?) = subscribeWhile(null, block)
|
||||
|
||||
|
||||
/**
|
||||
* 监听一个事件. 可同时进行多种方式的监听
|
||||
* @see ListenerBuilder
|
||||
*/
|
||||
fun <E : Event> KClass<E>.subscribeAll(listeners: ListenerBuilder<E>.() -> Unit) {
|
||||
ListenerBuilder<E> { this.subscribeInternal(it) }.apply(listeners)
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听构建器. 可同时进行多种方式的监听
|
||||
*
|
||||
* ```kotlin
|
||||
* FriendMessageEvent.subscribe {
|
||||
* always{
|
||||
* it.reply("永远发生")
|
||||
* }
|
||||
*
|
||||
* untilFalse {
|
||||
* it.reply("你发送了 ${it.message}")
|
||||
* it.message eq "停止"
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
inline class ListenerBuilder<out E : Event>(
|
||||
private val handlerConsumer: (Handler<in E>) -> Unit
|
||||
) {
|
||||
fun handler(block: suspend (E) -> ListeningStatus) {
|
||||
handlerConsumer(Handler(block))
|
||||
}
|
||||
|
||||
fun always(block: suspend (E) -> Unit) = handler { block(it); ListeningStatus.LISTENING }
|
||||
|
||||
fun <T> until(until: T, block: suspend (E) -> T) = handler { if (block(it) === until) ListeningStatus.STOPPED else ListeningStatus.LISTENING }
|
||||
fun untilFalse(block: suspend (E) -> Boolean) = until(false, block)
|
||||
fun untilTrue(block: suspend (E) -> Boolean) = until(true, block)
|
||||
fun untilNull(block: suspend (E) -> Any?) = until(null, block)
|
||||
|
||||
|
||||
fun <T> `while`(until: T, block: suspend (E) -> T) = handler { if (block(it) !== until) ListeningStatus.STOPPED else ListeningStatus.LISTENING }
|
||||
fun whileFalse(block: suspend (E) -> Boolean) = `while`(false, block)
|
||||
fun whileTrue(block: suspend (E) -> Boolean) = `while`(true, block)
|
||||
fun whileNull(block: suspend (E) -> Any?) = `while`(null, block)
|
||||
|
||||
|
||||
fun once(block: suspend (E) -> Unit) = handler { block(it); ListeningStatus.STOPPED }
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
package net.mamoe.mirai.event.events.bot
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.event.MiraiEvent
|
||||
import net.mamoe.mirai.event.Event
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
abstract class BotEvent(val bot: Bot) : MiraiEvent()
|
||||
abstract class BotEvent(val bot: Bot) : Event()
|
||||
|
||||
class BotLoginSucceedEvent(bot: Bot) : BotEvent(bot)
|
||||
class BotLoginSucceedEvent(bot: Bot) : BotEvent(bot) {
|
||||
companion object : KClass<BotLoginSucceedEvent> by BotLoginSucceedEvent::class
|
||||
}
|
@ -5,11 +5,13 @@ import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.message.Message
|
||||
import net.mamoe.mirai.message.defaults.MessageChain
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
class GroupMessageEvent(bot: Bot, group: Group, val sender: QQ, val message: MessageChain) : GroupEvent(bot, group) {
|
||||
companion object : KClass<GroupMessageEvent> by GroupMessageEvent::class
|
||||
|
||||
@JvmSynthetic
|
||||
suspend inline fun reply(message: Message) = group.sendMessage(message)
|
||||
|
@ -1,10 +1,22 @@
|
||||
package net.mamoe.mirai.event.events.network
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.event.Cancellable
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
abstract class ClientPacketEvent<out P : ClientPacket>(bot: Bot, packet: P) : PacketEvent<P>(bot, packet)
|
||||
|
||||
class PacketSentEvent(bot: Bot, packet: ClientPacket) : ClientPacketEvent<ClientPacket>(bot, packet)
|
||||
/**
|
||||
* 包已发送. 不可被取消
|
||||
*/
|
||||
class PacketSentEvent<P : ClientPacket>(bot: Bot, packet: P) : ClientPacketEvent<P>(bot, packet) {
|
||||
companion object : KClass<PacketSentEvent<*>> by PacketSentEvent::class
|
||||
}
|
||||
|
||||
class BeforePacketSendEvent(bot: Bot, packet: ClientPacket) : ClientPacketEvent<ClientPacket>(bot, packet)
|
||||
/**
|
||||
* 包发送前. 可被取消
|
||||
*/
|
||||
class BeforePacketSendEvent<P : ClientPacket>(bot: Bot, packet: P) : ClientPacketEvent<P>(bot, packet), Cancellable {
|
||||
companion object : KClass<BeforePacketSendEvent<*>> by BeforePacketSendEvent::class
|
||||
}
|
@ -2,8 +2,11 @@ package net.mamoe.mirai.event.events.network
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
abstract class ServerPacketEvent<out P : ServerPacket>(bot: Bot, packet: P) : PacketEvent<P>(bot, packet)
|
||||
|
||||
class ServerPacketReceivedEvent(bot: Bot, packet: ServerPacket) : ServerPacketEvent<ServerPacket>(bot, packet)
|
||||
class ServerPacketReceivedEvent(bot: Bot, packet: ServerPacket) : ServerPacketEvent<ServerPacket>(bot, packet) {
|
||||
companion object : KClass<ServerPacketReceivedEvent> by ServerPacketReceivedEvent::class
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package net.mamoe.mirai.event.events.qq;
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
@ -4,11 +4,15 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.message.Message
|
||||
import net.mamoe.mirai.message.defaults.MessageChain
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* 接受好友消息事件
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
class FriendMessageEvent(bot: Bot, sender: QQ, val message: MessageChain) : FriendEvent(bot, sender) {
|
||||
companion object : KClass<FriendMessageEvent> by FriendMessageEvent::class
|
||||
|
||||
@JvmSynthetic
|
||||
suspend inline fun reply(message: Message) = sender.sendMessage(message)
|
||||
@ -20,5 +24,5 @@ class FriendMessageEvent(bot: Bot, sender: QQ, val message: MessageChain) : Frie
|
||||
suspend inline fun reply(message: List<Message>) = sender.sendMessage(message)
|
||||
|
||||
@JvmSynthetic
|
||||
suspend inline fun reply(message: MessageChain) = sender.sendMessage(message)
|
||||
suspend inline fun reply(message: MessageChain) = sender.sendMessage(message)//shortcut
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
package net.mamoe.mirai.event.events.server;
|
||||
|
||||
import net.mamoe.mirai.event.AsyncEvent;
|
||||
import net.mamoe.mirai.event.MiraiEvent;
|
||||
|
||||
public final class ServerDisabledEvent extends MiraiEvent implements AsyncEvent {
|
||||
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package net.mamoe.mirai.event.events.server;
|
||||
|
||||
import net.mamoe.mirai.event.AsyncEvent;
|
||||
import net.mamoe.mirai.event.MiraiEvent;
|
||||
|
||||
public final class ServerEnabledEvent extends MiraiEvent implements AsyncEvent {
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package net.mamoe.mirai.event.internal
|
||||
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.event.ListeningStatus
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.allSuperclasses
|
||||
|
||||
/**
|
||||
* 监听和广播实现
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
|
||||
internal fun <E : Event> KClass<E>.subscribeInternal(listener: Listener<E>) = this.listeners.add(listener)
|
||||
|
||||
/**
|
||||
* 事件监听器
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
internal interface Listener<in E : Event> {
|
||||
|
||||
suspend fun onEvent(event: E): ListeningStatus
|
||||
}
|
||||
|
||||
/**
|
||||
* Lambda 监听器.
|
||||
* 不推荐直接使用该类
|
||||
*/
|
||||
class Handler<E : Event>(val handler: suspend (E) -> ListeningStatus) : Listener<E> {
|
||||
override suspend fun onEvent(event: E): ListeningStatus = handler.invoke(event)
|
||||
}
|
||||
|
||||
internal val <E : Event> KClass<E>.listeners: EventListeners<E> get() = EventListenerManger.get(this)
|
||||
|
||||
internal class EventListeners<E : Event> : MutableList<Listener<E>> by mutableListOf()
|
||||
|
||||
internal object EventListenerManger {
|
||||
private val REGISTRIES: MutableMap<KClass<out Event>, EventListeners<out Event>> = mutableMapOf()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun <E : Event> get(clazz: KClass<E>): EventListeners<E> {
|
||||
synchronized(clazz) {
|
||||
if (REGISTRIES.containsKey(clazz)) {
|
||||
return REGISTRIES[clazz] as EventListeners<E>
|
||||
} else {
|
||||
EventListeners<E>().let {
|
||||
REGISTRIES[clazz] = it
|
||||
return it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal suspend fun <E : Event> E.broadcastInternal(): E {
|
||||
suspend fun callListeners(listeners: EventListeners<in E>) {
|
||||
val iterator = listeners.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
if (iterator.next().onEvent(this) == ListeningStatus.STOPPED) {
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callListeners(this::class.listeners as EventListeners<E>)
|
||||
this::class.allSuperclasses.forEach {
|
||||
//println("super: " + it.simpleName)
|
||||
if (Event::class.java.isAssignableFrom(it.java)) {
|
||||
callListeners((it as KClass<out Event>).listeners as EventListeners<in E>)
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package net.mamoe.mirai.network
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.BotSocket
|
||||
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.Login
|
||||
import net.mamoe.mirai.network.protocol.tim.handler.ActionPacketHandler
|
||||
@ -12,7 +13,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
|
||||
import java.io.Closeable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
* Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务.
|
||||
@ -54,7 +54,7 @@ interface BotNetworkHandler : Closeable {
|
||||
*/
|
||||
val action: ActionPacketHandler
|
||||
|
||||
fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableFuture<LoginState>
|
||||
fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableDeferred<LoginState>
|
||||
|
||||
/**
|
||||
* 添加一个临时包处理器
|
||||
|
@ -2,6 +2,7 @@ package net.mamoe.mirai.network.protocol.tim
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.bot.BotLoginSucceedEvent
|
||||
import net.mamoe.mirai.event.events.network.BeforePacketSendEvent
|
||||
import net.mamoe.mirai.event.events.network.PacketSentEvent
|
||||
@ -58,9 +59,9 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
|
||||
*
|
||||
* @param touchingTimeoutMillis 连接每个服务器的 timeout
|
||||
*/
|
||||
override fun tryLogin(touchingTimeoutMillis: Long): CompletableFuture<LoginState> {
|
||||
override fun tryLogin(touchingTimeoutMillis: Long): CompletableDeferred<LoginState> {
|
||||
val ipQueue: LinkedList<String> = LinkedList(TIMProtocol.SERVER_IP)
|
||||
val future = CompletableFuture<LoginState>()
|
||||
val future = CompletableDeferred<LoginState>()
|
||||
|
||||
fun login() {
|
||||
this.socket.close()
|
||||
@ -133,7 +134,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (ServerPacketReceivedEvent(bot, packet).broadcast().isCancelled) {
|
||||
if (ServerPacketReceivedEvent(bot, packet).broadcast().cancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -222,7 +223,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
|
||||
try {
|
||||
packet.encodePacket()
|
||||
|
||||
if (BeforePacketSendEvent(bot, packet).broadcast().isCancelled) {
|
||||
if (BeforePacketSendEvent(bot, packet).broadcast().cancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -263,7 +264,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
|
||||
private lateinit var token0825: ByteArray//56
|
||||
private var loginTime: Int = 0
|
||||
private lateinit var loginIP: String
|
||||
private var randomTgtgtKey: ByteArray = getRandomByteArray(16)
|
||||
private var randomprivateKey: ByteArray = getRandomByteArray(16)
|
||||
|
||||
/**
|
||||
* 0828_decr_key
|
||||
@ -288,7 +289,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
|
||||
this.loginTime = packet.loginTime
|
||||
this.token0825 = packet.token0825
|
||||
println("token0825=" + this.token0825.toUHexString())
|
||||
socket.sendPacket(ClientPasswordSubmissionPacket(bot.account.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.randomTgtgtKey, packet.token0825))
|
||||
socket.sendPacket(ClientPasswordSubmissionPacket(bot.account.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.randomprivateKey, packet.token0825))
|
||||
}
|
||||
}
|
||||
|
||||
@ -299,9 +300,9 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
|
||||
}
|
||||
|
||||
is ServerCaptchaCorrectPacket -> {
|
||||
this.randomTgtgtKey = getRandomByteArray(16)
|
||||
this.randomprivateKey = getRandomByteArray(16)
|
||||
this.token00BA = packet.token00BA
|
||||
socket.sendPacket(ClientLoginResendPacket3105(bot.account.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.randomTgtgtKey, this.token0825, this.token00BA))
|
||||
socket.sendPacket(ClientLoginResendPacket3105(bot.account.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.randomprivateKey, this.token0825, this.token00BA))
|
||||
}
|
||||
|
||||
is ServerLoginResponseVerificationCodeInitPacket -> {
|
||||
@ -309,7 +310,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
|
||||
this.token00BA = packet.token00BA
|
||||
this.captchaCache = packet.verifyCodePart1
|
||||
|
||||
if (packet.unknownBoolean != null && packet.unknownBoolean!!) {
|
||||
if (packet.unknownBoolean == true) {
|
||||
this.captchaSectionId = 1
|
||||
socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.account.qqNumber, this.token0825, this.captchaSectionId++, this.token00BA))
|
||||
}
|
||||
@ -326,6 +327,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
|
||||
this.token00BA = packet.token00BA
|
||||
|
||||
if (packet.transmissionCompleted) {
|
||||
//todo 验证码多样化处理
|
||||
withContext(Dispatchers.IO) {
|
||||
bot.notice(CharImageUtil.createCharImg(ImageIO.read(captchaCache!!.inputStream())))
|
||||
}
|
||||
@ -363,11 +365,11 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
|
||||
//println("token00BA changed!!! to " + token00BA.toUByteArray())
|
||||
//}
|
||||
if (packet.flag == ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`) {
|
||||
this.randomTgtgtKey = packet.tgtgtKey
|
||||
socket.sendPacket(ClientLoginResendPacket3104(bot.account.qqNumber, bot.account.password, loginTime, loginIP, randomTgtgtKey, token0825, packet.tokenUnknown
|
||||
this.randomprivateKey = packet.privateKey
|
||||
socket.sendPacket(ClientLoginResendPacket3104(bot.account.qqNumber, bot.account.password, loginTime, loginIP, randomprivateKey, token0825, packet.tokenUnknown
|
||||
?: this.token00BA, packet.tlv0006))
|
||||
} else {
|
||||
socket.sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, randomTgtgtKey, token0825, packet.tokenUnknown
|
||||
socket.sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, randomprivateKey, token0825, packet.tokenUnknown
|
||||
?: token00BA, packet.tlv0006))
|
||||
}
|
||||
}
|
||||
@ -389,7 +391,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
|
||||
BotLoginSucceedEvent(bot).broadcast()
|
||||
|
||||
//登录成功后会收到大量上次的消息, 忽略掉 todo 优化
|
||||
GlobalScope.launch {
|
||||
NetworkScope.launch {
|
||||
delay(3000)
|
||||
message.ignoreMessage = false
|
||||
}
|
||||
@ -401,8 +403,8 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
|
||||
|
||||
is ServerCatchaPacket.Encrypted -> socket.distributePacket(packet.decrypt())
|
||||
is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> socket.distributePacket(packet.decrypt())
|
||||
is ServerLoginResponseKeyExchangePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.randomTgtgtKey))
|
||||
is ServerLoginResponseSuccessPacket.Encrypted -> socket.distributePacket(packet.decrypt(this.randomTgtgtKey))
|
||||
is ServerLoginResponseKeyExchangePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.randomprivateKey))
|
||||
is ServerLoginResponseSuccessPacket.Encrypted -> socket.distributePacket(packet.decrypt(this.randomprivateKey))
|
||||
is ServerSessionKeyResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.sessionResponseDecryptionKey))
|
||||
is ServerTouchResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt())
|
||||
|
||||
@ -417,10 +419,8 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fun changeOnlineStatus(status: ClientLoginStatus) {
|
||||
NetworkScope.launch {
|
||||
socket.sendPacket(ClientChangeOnlineStatusPacket(bot.account.qqNumber, sessionKey, status))
|
||||
}
|
||||
suspend fun changeOnlineStatus(status: ClientLoginStatus) {
|
||||
socket.sendPacket(ClientChangeOnlineStatusPacket(bot.account.qqNumber, sessionKey, status))
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
|
@ -2,6 +2,7 @@ package net.mamoe.mirai.network.protocol.tim.handler
|
||||
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.group.GroupMessageEvent
|
||||
import net.mamoe.mirai.event.events.qq.FriendMessageEvent
|
||||
import net.mamoe.mirai.getGroupByNumber
|
||||
|
@ -121,7 +121,7 @@ fun DataOutputStream.encryptAndWrite(keyHex: String, encoder: (ByteArrayDataOutp
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray) {
|
||||
fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray) {
|
||||
val firstMD5 = md5(password)
|
||||
val secondMD5 = md5(firstMD5 + "00 00 00 00".hexToBytes() + qq.toUInt().toByteArray())
|
||||
|
||||
@ -140,7 +140,7 @@ fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, lo
|
||||
it.writeZero(8)
|
||||
it.writeHex("00 10")//这两个hex是passwordSubmissionTLV2的末尾
|
||||
it.writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")//16
|
||||
it.write(tgtgtKey)
|
||||
it.write(privateKey)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ package net.mamoe.mirai.network.protocol.tim.packet
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent
|
||||
import net.mamoe.mirai.event.hookWhile
|
||||
import net.mamoe.mirai.event.subscribeWhileTrue
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.PacketNameFormatter.adjustName
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerCanAddFriendResponsePacket
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendFriendMessageResponsePacket
|
||||
@ -337,7 +337,7 @@ fun DataInputStream.gotoWhere(matcher: ByteArray): DataInputStream {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun <P : ServerPacket> Bot.waitForPacket(packetClass: KClass<P>, timeoutMillis: Long = Long.MAX_VALUE, timeout: () -> Unit = {}) {
|
||||
var got = false
|
||||
ServerPacketReceivedEvent::class.hookWhile {
|
||||
ServerPacketReceivedEvent::class.subscribeWhileTrue {
|
||||
if (packetClass.isInstance(it.packet) && it.bot === this) {
|
||||
got = true
|
||||
true
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.mamoe.mirai.network.protocol.tim.packet
|
||||
|
||||
import net.mamoe.mirai.utils.LoggerTextFormat
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
import java.io.DataInputStream
|
||||
|
||||
@ -9,7 +10,7 @@ import java.io.DataInputStream
|
||||
*/
|
||||
class UnknownServerPacket(input: DataInputStream) : ServerPacket(input) {
|
||||
override fun decode() {
|
||||
println("UnknownServerPacket data: " + this.input.goto(0).readAllBytes().toUHexString())
|
||||
MiraiLogger.debug("UnknownServerPacket data: " + this.input.goto(0).readAllBytes().toUHexString())
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
|
@ -15,7 +15,6 @@ import java.util.*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@PacketId("00 A7")
|
||||
|
||||
class ClientCanAddFriendPacket(
|
||||
val bot: Long,
|
||||
val qq: Long,
|
||||
@ -80,7 +79,6 @@ class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(in
|
||||
* 请求添加好友
|
||||
*/
|
||||
@PacketId("00 AE")
|
||||
|
||||
class ClientAddFriendPacket(
|
||||
val bot: Long,
|
||||
val qq: Long,
|
||||
|
@ -10,7 +10,6 @@ import java.io.DataInputStream
|
||||
* @author Him188moe
|
||||
*/
|
||||
@PacketId("00 CD")
|
||||
|
||||
class ClientSendFriendMessagePacket(
|
||||
private val botQQ: Long,
|
||||
private val targetQQ: Long,
|
||||
|
@ -19,7 +19,7 @@ class ClientPasswordSubmissionPacket(
|
||||
private val password: String,
|
||||
private val loginTime: Int,
|
||||
private val loginIP: String,
|
||||
private val tgtgtKey: ByteArray,//16 random by client
|
||||
private val privateKey: ByteArray,//16 random by client
|
||||
private val token0825: ByteArray//56 from server
|
||||
) : ClientPacket() {
|
||||
|
||||
@ -34,37 +34,34 @@ class ClientPasswordSubmissionPacket(
|
||||
this.writeHex(TIMProtocol.key0836)
|
||||
|
||||
this.encryptAndWrite(TIMProtocol.shareKey.hexToBytes()) {
|
||||
it.writePart1(qq, password, loginTime, loginIP, tgtgtKey, token0825)
|
||||
it.writePart1(qq, password, loginTime, loginIP, privateKey, token0825)
|
||||
it.writePart2()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PacketId("08 36 31 04")
|
||||
|
||||
class ClientLoginResendPacket3104(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null)
|
||||
: ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, tlv0006)
|
||||
class ClientLoginResendPacket3104(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null)
|
||||
: ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, tlv0006)
|
||||
|
||||
@PacketId("08 36 31 05")
|
||||
|
||||
class ClientLoginResendPacket3105(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray)
|
||||
: ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, null)
|
||||
class ClientLoginResendPacket3105(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray)
|
||||
: ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, null)
|
||||
|
||||
@PacketId("08 36 31 06")
|
||||
|
||||
class ClientLoginResendPacket3106(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null)
|
||||
: ClientLoginResendPacket(qq, password, loginTime, loginIP, tgtgtKey, token0825, token00BA, tlv0006)
|
||||
class ClientLoginResendPacket3106(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null)
|
||||
: ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, tlv0006)
|
||||
|
||||
|
||||
open class ClientLoginResendPacket internal constructor(
|
||||
val qq: Long,
|
||||
val password: String,
|
||||
val loginTime: Int,
|
||||
val loginIP: String,
|
||||
val tgtgtKey: ByteArray,
|
||||
val token0825: ByteArray,
|
||||
val token00BA: ByteArray,
|
||||
val tlv0006: ByteArray? = null
|
||||
private val qq: Long,
|
||||
private val password: String,
|
||||
private val loginTime: Int,
|
||||
private val loginIP: String,
|
||||
private val privateKey: ByteArray,
|
||||
private val token0825: ByteArray,
|
||||
private val token00BA: ByteArray,
|
||||
private val tlv0006: ByteArray? = null
|
||||
) : ClientPacket() {
|
||||
override fun encode() {
|
||||
this.writeQQ(qq)
|
||||
@ -77,7 +74,7 @@ open class ClientLoginResendPacket internal constructor(
|
||||
this.writeHex(TIMProtocol.key0836)//16
|
||||
|
||||
this.encryptAndWrite(TIMProtocol.shareKey.hexToBytes()) {
|
||||
it.writePart1(qq, password, loginTime, loginIP, tgtgtKey, token0825, tlv0006)
|
||||
it.writePart1(qq, password, loginTime, loginIP, privateKey, token0825, tlv0006)
|
||||
|
||||
it.writeHex("01 10") //tag
|
||||
it.writeHex("00 3C")//length
|
||||
@ -94,8 +91,7 @@ open class ClientLoginResendPacket internal constructor(
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
|
||||
private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: Int, loginIP: String, tgtgtKey: ByteArray, token0825: ByteArray, tlv0006: ByteArray? = null) {
|
||||
private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, tlv0006: ByteArray? = null) {
|
||||
|
||||
//this.writeInt(System.currentTimeMillis().toInt())
|
||||
this.writeHex("01 12")//tag
|
||||
@ -111,13 +107,13 @@ private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: I
|
||||
if (tlv0006 != null) {
|
||||
this.write(tlv0006)
|
||||
} else {
|
||||
this.writeTLV0006(qq, password, loginTime, loginIP, tgtgtKey)
|
||||
this.writeTLV0006(qq, password, loginTime, loginIP, privateKey)
|
||||
}
|
||||
//fix
|
||||
this.writeHex(TIMProtocol.passwordSubmissionTLV2)
|
||||
this.writeHex("00 1A")//tag
|
||||
this.writeHex("00 40")//length
|
||||
this.write(TEA.encrypt(TIMProtocol.passwordSubmissionTLV2.hexToBytes(), tgtgtKey))
|
||||
this.write(TEA.encrypt(TIMProtocol.passwordSubmissionTLV2.hexToBytes(), privateKey))
|
||||
this.writeHex(TIMProtocol.constantData1)
|
||||
this.writeHex(TIMProtocol.constantData2)
|
||||
this.writeQQ(qq)
|
||||
|
@ -8,7 +8,7 @@ import net.mamoe.mirai.utils.Tested
|
||||
import java.io.DataInputStream
|
||||
|
||||
/**
|
||||
* 服务器进行加密后返回 tgtgtKey
|
||||
* 服务器进行加密后返回 privateKey
|
||||
*
|
||||
* @author NaturalHG
|
||||
*/
|
||||
@ -21,12 +21,12 @@ class ServerLoginResponseKeyExchangePacket(input: DataInputStream, val flag: Fla
|
||||
|
||||
lateinit var tlv0006: ByteArray;//120bytes
|
||||
var tokenUnknown: ByteArray? = null
|
||||
lateinit var tgtgtKey: ByteArray//16bytes
|
||||
lateinit var privateKey: ByteArray//16bytes
|
||||
|
||||
@Tested
|
||||
override fun decode() {
|
||||
this.input.skip(5)
|
||||
tgtgtKey = this.input.readNBytes(16)//22
|
||||
privateKey = this.input.readNBytes(16)//22
|
||||
//this.input.skip(2)//25
|
||||
this.input.goto(25)
|
||||
tlv0006 = this.input.readNBytes(120)
|
||||
@ -48,8 +48,8 @@ class ServerLoginResponseKeyExchangePacket(input: DataInputStream, val flag: Fla
|
||||
class Encrypted(input: DataInputStream, private val flag: Flag) : ServerPacket(input) {
|
||||
|
||||
@Tested
|
||||
fun decrypt(tgtgtKey: ByteArray): ServerLoginResponseKeyExchangePacket {
|
||||
return ServerLoginResponseKeyExchangePacket(this.decryptBy(TIMProtocol.shareKey, tgtgtKey), flag).setId(this.idHex)
|
||||
fun decrypt(privateKey: ByteArray): ServerLoginResponseKeyExchangePacket {
|
||||
return ServerLoginResponseKeyExchangePacket(this.decryptBy(TIMProtocol.shareKey, privateKey), flag).setId(this.idHex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,9 +55,9 @@ class ServerLoginResponseSuccessPacket(input: DataInputStream) : ServerPacket(in
|
||||
|
||||
class Encrypted(input: DataInputStream) : ServerPacket(input) {
|
||||
|
||||
fun decrypt(tgtgtKey: ByteArray): ServerLoginResponseSuccessPacket {
|
||||
fun decrypt(privateKey: ByteArray): ServerLoginResponseSuccessPacket {
|
||||
input goto 14
|
||||
return ServerLoginResponseSuccessPacket(this.decryptBy(TIMProtocol.shareKey, tgtgtKey)).setId(this.idHex)
|
||||
return ServerLoginResponseSuccessPacket(this.decryptBy(TIMProtocol.shareKey, privateKey)).setId(this.idHex)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import net.mamoe.mirai.utils.hexToUBytes
|
||||
import java.io.DataInputStream
|
||||
|
||||
/**
|
||||
* 收到这个包意味着需要验证码登录, 并且能得到验证码图片文件的一半
|
||||
* 收到这个包意味着需要验证码登录, 并且能得到验证码图片文件的一部分
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@ -23,7 +23,6 @@ class ServerLoginResponseVerificationCodeInitPacket(input: DataInputStream, priv
|
||||
|
||||
|
||||
@Tested
|
||||
|
||||
override fun decode() {
|
||||
val verifyCodeLength = this.input.goto(78).readShort()//2bytes
|
||||
this.verifyCodePart1 = this.input.readNBytes(verifyCodeLength.toInt())
|
||||
@ -41,7 +40,6 @@ class ServerLoginResponseVerificationCodeInitPacket(input: DataInputStream, priv
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun decrypt(): ServerLoginResponseVerificationCodeInitPacket {
|
||||
this.input goto 14
|
||||
val data = this.decryptBy(TIMProtocol.shareKey).goto(0).readAllBytes()
|
||||
|
@ -1,9 +1,5 @@
|
||||
package net.mamoe.mirai.task;
|
||||
|
||||
|
||||
import net.mamoe.mirai.event.MiraiEventHook;
|
||||
import net.mamoe.mirai.event.events.server.ServerDisabledEvent;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@ -25,12 +21,6 @@ public final class MiraiTaskManager {
|
||||
|
||||
private MiraiTaskManager() {
|
||||
this.pool = new MiraiThreadPool();
|
||||
|
||||
MiraiEventHook
|
||||
.onEvent(ServerDisabledEvent.class)
|
||||
.handler(a -> this.pool.close())
|
||||
.mount();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,9 +0,0 @@
|
||||
package net.mamoe.mirai.utils;
|
||||
|
||||
import net.mamoe.mirai.contact.Contact;
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
public class ContactList<C extends Contact> extends MiraiSynchronizedLinkedHashMap<Long, C> {
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
class ContactList<C : Contact> : MiraiSynchronizedLinkedHashMap<Long, C>()
|
@ -6,12 +6,11 @@ import net.mamoe.mirai.network.protocol.tim.packet.goto
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
interface MiraiLogger {
|
||||
companion object : Console("[TOP Level]")
|
||||
companion object : MiraiLogger by defaultLogger()
|
||||
|
||||
var identity: String
|
||||
|
||||
@ -31,6 +30,8 @@ interface MiraiLogger {
|
||||
fun blue(any: Any?)
|
||||
}
|
||||
|
||||
private fun defaultLogger(): MiraiLogger = Console("[TOP Level]")
|
||||
|
||||
val DEBUGGING: Boolean by lazy {
|
||||
//avoid inspections
|
||||
true
|
||||
@ -70,7 +71,7 @@ fun Bot.notice(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_BLUE)
|
||||
fun Bot.purple(o: Any?) = print(this, o.toString(), LoggerTextFormat.PURPLE)
|
||||
|
||||
fun Bot.cyan(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_CYAN)
|
||||
fun Bot.success(o: Any?) = print(this, o.toString(), LoggerTextFormat.GREEN)
|
||||
fun Bot.green(o: Any?) = print(this, o.toString(), LoggerTextFormat.GREEN)
|
||||
|
||||
fun Bot.debug(o: Any?) = print(this, o.toString(), LoggerTextFormat.YELLOW)
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
|
||||
import net.mamoe.mirai.utils.BotAccount
|
||||
import net.mamoe.mirai.utils.Console
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* 筛选掉无法登录(冻结/设备锁/UNKNOWN)的 qq
|
||||
@ -85,7 +85,9 @@ suspend fun main() {
|
||||
)
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
bot.network.tryLogin().get(3, TimeUnit.MILLISECONDS)
|
||||
withTimeout(3000) {
|
||||
bot.network.tryLogin().await()
|
||||
}
|
||||
}.let { state ->
|
||||
if (!(state == LoginState.BLOCKED || state == LoginState.DEVICE_LOCK || state == LoginState.WRONG_PASSWORD)) {
|
||||
goodBotList.add(bot)
|
||||
|
@ -18,7 +18,7 @@ import java.io.DataInputStream
|
||||
* @author Him188moe
|
||||
*/
|
||||
object Main {
|
||||
val localIp = "192.168.3.10"
|
||||
const val localIp = "192.168.3.10"
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
@ -99,12 +99,12 @@ object Main {
|
||||
println("login failed")
|
||||
}
|
||||
|
||||
is ServerLoginResponseKeyExchangePacket.Encrypted -> packetReceived(packet.decrypt(tgtgtKey))
|
||||
is ServerLoginResponseKeyExchangePacket.Encrypted -> packetReceived(packet.decrypt(privateKey))
|
||||
is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> packetReceived(packet.decrypt())
|
||||
is ServerLoginResponseSuccessPacket.Encrypted -> packetReceived(packet.decrypt(tgtgtKey))
|
||||
is ServerLoginResponseSuccessPacket.Encrypted -> packetReceived(packet.decrypt(privateKey))
|
||||
|
||||
is ServerLoginResponseKeyExchangePacket -> {
|
||||
tgtgtKey = packet.tgtgtKey
|
||||
privateKey = packet.privateKey
|
||||
//then 31 04 or 31 06
|
||||
}
|
||||
|
||||
@ -133,7 +133,7 @@ object Main {
|
||||
lateinit var token0825: ByteArray//56
|
||||
var loginTime: Int = 0
|
||||
lateinit var loginIp: String
|
||||
lateinit var tgtgtKey: ByteArray//16
|
||||
lateinit var privateKey: ByteArray//16
|
||||
lateinit var sessionKey: ByteArray
|
||||
|
||||
lateinit var sessionResponseDecryptionKey: ByteArray
|
||||
@ -177,9 +177,9 @@ object Main {
|
||||
tlv0006.skip(3)
|
||||
tlv0006.skip(16 + 4 + 1 + 4 * 3 + 4 + 8 + 2)
|
||||
tlv0006.skipHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")
|
||||
tgtgtKey = tlv0006.readNBytes(16)
|
||||
privateKey = tlv0006.readNBytes(16)
|
||||
}
|
||||
println("Got tgtgtKey=" + tgtgtKey.toUHexString())
|
||||
println("Got privateKey=" + privateKey.toUHexString())
|
||||
|
||||
//then receive
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ flag = 08 25 31 02
|
||||
得到m_loginTime = 5D 61 20 F9
|
||||
得到m_loginIP = AB 70 E2 96
|
||||
得到m_0825token = BF A4 B1 36 7C 5D 5A A6 3B 2E 65 E9 20 59 7B 04 3B 7C 36 A2 0F 56 79 BA 9D 66 95 08 F3 B1 56 80 AF C6 C7 C0 AC A8 C2 39 1C AC B5 D5 F8 CD 47 F7 33 06 96 85 1F BA 6E AF
|
||||
得到m_tgtgtKey = CE AD BA 22 50 A9 FE 62 AF C4 29 86 2C AF 17 5C
|
||||
得到m_privateKey = CE AD BA 22 50 A9 FE 62 AF C4 29 86 2C AF 17 5C
|
||||
发送 ClientPasswordSubmissionPacket
|
||||
PCName 取主机名 () = DESKTOP-M17JREU
|
||||
PCName 处理后 = 44 45 53 4B 54 4F 50 2D 4D 31 37 4A 52 45 55
|
||||
@ -27,7 +27,7 @@ g_pass = xiaoqqq
|
||||
g_QQ = 76 E4 B8 DD
|
||||
crc32_code(Random) = B9 11 E7 AB 9D AB 61 35 4B B2 2D E0 EA 78 C6 C0
|
||||
crc32_data = 95 9D B2 30
|
||||
m_tgtgtKey = CE AD BA 22 50 A9 FE 62 AF C4 29 86 2C AF 17 5C
|
||||
m_privateKey = CE AD BA 22 50 A9 FE 62 AF C4 29 86 2C AF 17 5C
|
||||
packet = 79 35 21 65 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 95 5B 96 CB 95 CF 1C A6 94 C4 B7 79 07 9A BB 15 5D 61 20 F9 00 00 00 00 00 00 00 00 00 00 00 00 00 AB 70 E2 96 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B CE AD BA 22 50 A9 FE 62 AF C4 29 86 2C AF 17 5C
|
||||
TEA 加密, 内容=79 35 21 65 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 95 5B 96 CB 95 CF 1C A6 94 C4 B7 79 07 9A BB 15 5D 61 20 F9 00 00 00 00 00 00 00 00 00 00 00 00 00 AB 70 E2 96 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B CE AD BA 22 50 A9 FE 62 AF C4 29 86 2C AF 17 5C
|
||||
TEA 加密, 内容=00 15 00 30 00 01 01 27 9B C7 F5 00 10 65 03 FD 8B 00 00 00 00 00 00 00 00 00 00 00 00 02 90 49 55 33 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B
|
||||
@ -39,7 +39,7 @@ g_pass = xiaoqqq
|
||||
g_QQ = 76 E4 B8 DD
|
||||
crc32_code(Random) = A0 99 89 10 F4 AB AD F5 C1 03 CE 74 FE FB 49 FD
|
||||
crc32_data = 5B 1F 17 CA
|
||||
m_tgtgtKey = CE AD BA 22 50 A9 FE 62 AF C4 29 86 2C AF 17 5C
|
||||
m_privateKey = CE AD BA 22 50 A9 FE 62 AF C4 29 86 2C AF 17 5C
|
||||
packet = A5 FD E6 15 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 95 5B 96 CB 95 CF 1C A6 94 C4 B7 79 07 9A BB 15 5D 61 20 F9 00 00 00 00 00 00 00 00 00 00 00 00 00 AB 70 E2 96 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B CE AD BA 22 50 A9 FE 62 AF C4 29 86 2C AF 17 5C
|
||||
TEA 加密, 内容=A5 FD E6 15 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 95 5B 96 CB 95 CF 1C A6 94 C4 B7 79 07 9A BB 15 5D 61 20 F9 00 00 00 00 00 00 00 00 00 00 00 00 00 AB 70 E2 96 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B CE AD BA 22 50 A9 FE 62 AF C4 29 86 2C AF 17 5C
|
||||
TEA 加密, 内容=00 15 00 30 00 01 01 27 9B C7 F5 00 10 65 03 FD 8B 00 00 00 00 00 00 00 00 00 00 00 00 02 90 49 55 33 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B
|
||||
@ -49,7 +49,7 @@ paccket sent: 02 37 13 08 36 31 03 76 E4 B8 DD 03 00 00 00 01 01 01 00 00 68 20
|
||||
flag = 08 36 31 03
|
||||
收到 ServerLoginResponseSucceedPacket
|
||||
原文 = 02 37 13 08 36 31 03 76 E4 B8 DD 00 00 00 BD 57 C9 45 45 E4 52 BE DA 05 67 A3 49 0F B7 90 AD 3E 47 34 A9 A8 B3 D9 82 E1 45 95 A4 41 F0 66 56 20 D5 0C B7 AF 9E A3 3A 32 FE 89 B2 0A AD 81 EC D0 D1 7A 17 00 51 5E FA BD 75 D1 DB E9 12 DC 89 25 A8 6D 80 F4 00 21 68 70 A0 77 E3 EF FA 9C 80 25 47 5B 55 E1 A0 1D D9 6B FE B7 7F 6A 3B 67 45 A5 F1 CE 33 F4 43 67 1D FD 83 F6 88 9F 2E 7E F3 8B 0E DE 68 76 B1 48 9A 5C B2 B2 8D 12 E3 FA CE 0F 22 F1 7C 20 4D AD 01 09 36 C6 64 3A BE CA 33 68 46 19 8A A9 66 7A 13 DC F2 EE 04 91 74 FB CB 57 B5 48 84 BF 99 24 3C 1E 5C 04 56 F1 28 E1 49 95 0D 71 39 FB A2 AE EE C5 E6 99 91 A3 A3 59 48 CA DE 10 66 F2 FA 88 D8 6F 46 2B B3 F4 33 C0 64 92 92 99 83 06 43 C2 3C FC 0F 34 38 7C 0F F8 3C 35 D0 CD 23 05 06 5B 61 B3 AD 38 D9 E2 5F 51 A4 A0 CA AF 4A A9 86 11 C9 AC 2C 44 11 08 52 E3 3C 0D 1B 91 B6 C6 70 FC 15 CC 16 F6 3B C9 97 C0 82 D1 8B 24 2F AA 35 50 61 E9 11 F8 E1 09 29 B9 20 5E 3A 73 33 BF 78 9C CC D0 A7 BF 23 66 65 3B D1 1F 71 40 C2 E2 0D CA 6F 57 D8 E1 46 B6 47 65 9E 43 04 0E 30 54 EB 70 42 49 6F 75 55 C5 63 3A A4 9D FF 0B F8 56 3B 89 74 14 56 6B 6E 9D 32 D4 DD FA E7 C6 B2 6B 61 F0 54 EF 05 7E E1 49 D6 38 A0 C1 B3 F6 B4 7A 4A 03 31 1D E7 88 BA 56 9D 50 03 95 FF DB 23 DC 3C B9 51 1B 4B 06 1E 5E C5 B1 96 EA 8B 64 92 48 24 65 A4 92 EC BA 90 42 AD BA 04 81 4F 42 FB 41 60 E9 93 68 1F 59 67 57 57 5F 40 22 1B F2 D9 C5 5D 53 34 2C E4 82 ED D2 A8 3B F1 C2 05 2A 4D F9 45 63 21 E7 92 5A 01 D4 A4 3A 98 D8 57 39 34 D4 E2 CC F6 D1 76 12 76 00 A5 89 18 66 9C F9 18 31 52 E6 92 B2 11 46 73 8D 37 92 99 7A 3B FC 82 36 A1 7A 7B 91 D0 F7 59 C2 64 76 7A 4F 7E 88 8A AF 11 AA 90 5C 0D E2 9F F7 A8 9B 04 A7 05 48 EC 92 01 2A 19 0E 8C A7 1B 9C 1F B2 F8 BD C5 AE 98 D5 86 C7 C6 D2 D5 BC B5 BB D7 F9 05 52 F1 5A 6D B5 94 2C 44 86 11 A9 B3 EB 9D D7 30 BF 21 1F 22 2D FB AC 0C 5C 94 C4 69 C2 82 C8 48 6C 86 40 95 EF 67 9B B1 60 17 09 56 AE CB 85 EF FD 60 7D BA A3 1D 13 05 10 93 ED 5D 91 6B 3B 8C 23 C4 45 EF 02 BA 86 0E F7 8E 46 C7 3D 07 8A 67 94 3B 5C 4B 05 BD 64 76 DF 1A 3B A5 C9 26 AA F6 A5 36 4E EC 00 AD D8 B7 5E 32 53 02 9F CF 3C 23 9C 94 BB 03 F8 97 9F 53 CC A0 68 77 4D A4 DE D0 CE DE 68 FC A2 07 A5 9E 65 28 E2 A2 95 E0 1D 45 11 47 E9 03 1A BE F5 1F 48 36 37 B8 EA EA 6B 9C 73 93 7D 21 CA 77 F7 62 73 BF BA 54 BB C2 38 0C 04 68 A 4 E0 05 98 18 6E 5D EC 40 EE 54 27 9C 67 5C 79 5D 89 3C 4F DC 29 50 46 87 D9 EB F9 12 03
|
||||
进行解密, key=m_tgtgtKey = CE AD BA 22 50 A9 FE 62 AF C4 29 86 2C AF 17 5C
|
||||
进行解密, key=m_privateKey = CE AD BA 22 50 A9 FE 62 AF C4 29 86 2C AF 17 5C
|
||||
得到m_0828_rec_decr_key = 79 44 42 48 54 78 7D 76 35 2D 37 5F 74 58 77 66
|
||||
得到nick_length = 20
|
||||
得到m_nick = (?ω)
|
||||
|
30
mirai-core/src/test/java/event/EventTest.kt
Normal file
30
mirai-core/src/test/java/event/EventTest.kt
Normal file
@ -0,0 +1,30 @@
|
||||
package event
|
||||
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.subscribeAll
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
open class SuperEvent : Event() {
|
||||
companion object : KClass<SuperEvent> by SuperEvent::class//方便 subscribe
|
||||
}
|
||||
|
||||
open class ChildEvent : SuperEvent()
|
||||
|
||||
open class ChildChildEvent : ChildEvent()
|
||||
|
||||
class ChildChildChildEvent : ChildChildEvent()
|
||||
|
||||
|
||||
suspend fun main() {
|
||||
SuperEvent.subscribeAll {
|
||||
always {
|
||||
println(it.javaClass.simpleName)//ChildChildChildEvent
|
||||
}
|
||||
}
|
||||
|
||||
ChildChildChildEvent().broadcast()
|
||||
}
|
@ -4,7 +4,9 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.event.events.group.GroupMessageEvent
|
||||
import net.mamoe.mirai.event.events.qq.FriendMessageEvent
|
||||
import net.mamoe.mirai.event.hookAlways
|
||||
import net.mamoe.mirai.event.subscribeAll
|
||||
import net.mamoe.mirai.event.subscribeAlways
|
||||
import net.mamoe.mirai.event.subscribeUntilFalse
|
||||
import net.mamoe.mirai.message.defaults.Image
|
||||
import net.mamoe.mirai.message.defaults.PlainText
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
|
||||
@ -14,18 +16,29 @@ import net.mamoe.mirai.utils.Console
|
||||
/**
|
||||
* @author Him188moe
|
||||
*/
|
||||
fun main() {
|
||||
suspend fun main() {
|
||||
val bot = Bot(BotAccount(
|
||||
qqNumber = 1683921395,
|
||||
password = "bb22222"
|
||||
), Console())
|
||||
|
||||
bot.network.tryLogin().get().let {
|
||||
bot.network.tryLogin().await().let {
|
||||
check(it == LoginState.SUCCESS) { "Login failed: " + it.name }
|
||||
}
|
||||
|
||||
|
||||
//DSL 监听
|
||||
FriendMessageEvent.subscribeAll {
|
||||
always {
|
||||
//获取第一个纯文本消息
|
||||
val firstText = it.message[PlainText]
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//监听事件:
|
||||
FriendMessageEvent::class.hookAlways {
|
||||
FriendMessageEvent.subscribeAlways {
|
||||
//获取第一个纯文本消息
|
||||
val firstText = it.message[PlainText]
|
||||
|
||||
@ -57,9 +70,27 @@ fun main() {
|
||||
}
|
||||
}
|
||||
|
||||
GroupMessageEvent::class.hookAlways {
|
||||
GroupMessageEvent::class.subscribeAlways {
|
||||
when {
|
||||
it.message.contains("复读") -> it.reply(it.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 实现功能:
|
||||
* 对机器人说 "记笔记", 机器人记录之后的所有消息.
|
||||
* 对机器人说 "停止", 机器人停止
|
||||
*/
|
||||
fun demo2() {
|
||||
FriendMessageEvent.subscribeAlways { event ->
|
||||
if (event.message eq "记笔记") {
|
||||
FriendMessageEvent.subscribeUntilFalse {
|
||||
it.reply("你发送了 ${it.message}")
|
||||
|
||||
it.message eq "停止"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
|
||||
<modules>
|
||||
<module>mirai-demo-1</module>
|
||||
<module>mirai-demo-event</module>
|
||||
</modules>
|
||||
|
||||
|
||||
|
8
pom.xml
8
pom.xml
@ -34,7 +34,7 @@
|
||||
|
||||
|
||||
<properties>
|
||||
<kotlin.version>1.3.41</kotlin.version>
|
||||
<kotlin.version>1.3.50</kotlin.version>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@ -80,12 +80,6 @@
|
||||
<version>5.4.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.8.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core -->
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
|
Loading…
Reference in New Issue
Block a user