Commit b38c262d authored by Him188's avatar Him188

Multiplatform with gradle building

parent 58c6a407
...@@ -12,8 +12,6 @@ ...@@ -12,8 +12,6 @@
# Package Files # # Package Files #
*.war *.war
*.jar
!/mirai-debug/lib/jpcap.jar
*.nar *.nar
*.ear *.ear
*.zip *.zip
...@@ -23,6 +21,8 @@ ...@@ -23,6 +21,8 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid* hs_err_pid*
target/
build/
.idea/ .idea/
*.iml *.iml
...@@ -31,4 +31,7 @@ mirai.iml ...@@ -31,4 +31,7 @@ mirai.iml
.idea/* .idea/*
/.idea/* /.idea/*
/test /test
\ No newline at end of file
.gradle/
\ No newline at end of file
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
jcenter()
google()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
group = "net.mamoe"
version = "1.0"
repositories {
jcenter()
mavenCentral()
google()
maven { url "https://mirrors.huaweicloud.com/repository/maven/" }
maven { url "http://repo.maven.apache.org/maven2" }
}
apply from: rootProject.file('dependencies.gradle')
}
ext {
// dependencies
// kotlin
kotlinJvm = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
kotlinCommon = "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
// coroutine
coroutine_version = "1.3.0"
coroutine = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
coroutineCommon = "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutine_version"
coroutineNative = "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$coroutine_version"
coroutineAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
coroutineJs = "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutine_version"
// reflect
reflect = "org.jetbrains.kotlin:kotlin-reflect:$coroutine_version"
}
#Thu Oct 03 14:28:43 CST 2019
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
apply plugin: "kotlin"
apply plugin: "java"
dependencies {
implementation project(':mirai-core')
implementation project(':mirai-console')
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.mamoe</groupId>
<artifactId>mirai</artifactId>
<version>1.0</version>
</parent>
<artifactId>mirai-api</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>net.mamoe</groupId>
<artifactId>mirai-console</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>/src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
</plugins>
</build>
</project>
\ No newline at end of file
package net.mamoe.mirai; package net.mamoe.mirai;
import lombok.Getter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class Bot { public class Bot {
@Getter
private final long qq; private final long qq;
public Bot(long qq){ public Bot(long qq){
......
package net.mamoe.mirai;
import java.util.ArrayList;
import java.util.List;
/**
* MiraiAPI provides
* - 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) {
MiraiMain.main(args);
}
public static void closeMirai() {
MiraiServer.INSTANCE.shutdown();
}
public static void restartMirai(String[] args) {
MiraiServer.INSTANCE.shutdown();
MiraiMain.main(args);
}
public static String getMiraiVersion() {
return MiraiServer.MIRAI_VERSION;
}
public static String getQQPortocolVersion() {
return MiraiServer.QQ_VERSION;
}
public static boolean isMiraiEnabled() {
// TODO: 2019/10/2
return false;
}
public static List<String> getEnabledPluginList() {
return new ArrayList<>();
}
public static List<Long> getEnabledBots() {
return new ArrayList<>();
}
public static Bot getBot(long qq) {
return new Bot(qq);
}
public static void addBot(long qq, String password) {
}
}
apply plugin: "kotlin"
apply plugin: "application"
apply plugin: "java"
dependencies {
compile project(':mirai-core')
compile rootProject.ext.kotlinCommon
compile rootProject.ext.kotlinJvm
compile rootProject.ext.reflect
compile rootProject.ext.coroutine
}
sourceCompatibility = "11"
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.mamoe</groupId>
<artifactId>mirai</artifactId>
<version>1.0</version>
</parent>
<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.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>/src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<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>
</project>
\ No newline at end of file
package net.mamoe.mirai; package net.mamoe.mirai;
import lombok.Getter;
/** /**
* @author NaturalHG * @author NaturalHG
*/ */
public final class MiraiMain { public final class MiraiMain {
@Getter
private static MiraiServer server; private static MiraiServer server;
public static void main(String[] args) { public static void main(String[] args) {
......
package net.mamoe.mirai package net.mamoe.mirai
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import lombok.Getter
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState 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.*
import net.mamoe.mirai.utils.config.MiraiConfig import net.mamoe.mirai.utils.config.MiraiConfig
import net.mamoe.mirai.utils.setting.MiraiSettings import net.mamoe.mirai.utils.setting.MiraiSettings
...@@ -22,22 +20,11 @@ import java.util.concurrent.ExecutionException ...@@ -22,22 +20,11 @@ import java.util.concurrent.ExecutionException
* @author NaturalHG * @author NaturalHG
*/ */
object MiraiServer { 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 var isUnix: Boolean = false
private set private set
@Getter
var parentFolder: File = File(System.getProperty("user.dir")) var parentFolder: File = File(System.getProperty("user.dir"))
@Getter
var taskManager: MiraiTaskManager
internal set
@Getter
var logger: MiraiLogger var logger: MiraiLogger
internal set internal set
...@@ -52,9 +39,8 @@ object MiraiServer { ...@@ -52,9 +39,8 @@ object MiraiServer {
this.isUnix = !System.getProperties().getProperty("os.name").toUpperCase().contains("WINDOWS") this.isUnix = !System.getProperties().getProperty("os.name").toUpperCase().contains("WINDOWS")
this.logger = MiraiLogger this.logger = MiraiLogger
this.taskManager = MiraiTaskManager.getInstance()
logger.info("About to run Mirai (" + MiraiServer.MIRAI_VERSION + ") under " + if (isUnix) "unix" else "windows") logger.info("About to run Mirai (" + Mirai.VERSION + ") under " + if (isUnix) "unix" else "windows")
logger.info("Loading data under " + LoggerTextFormat.GREEN + this.parentFolder) logger.info("Loading data under " + LoggerTextFormat.GREEN + this.parentFolder)
val setting = this.parentFolder + "/Mirai.ini" val setting = this.parentFolder + "/Mirai.ini"
...@@ -117,17 +103,17 @@ object MiraiServer { ...@@ -117,17 +103,17 @@ object MiraiServer {
this.settings = MiraiSettings(setting) this.settings = MiraiSettings(setting)
val network = this.settings.getMapSection("network") val network = this.settings.getMapSection("network")
network.set("enable_proxy", "not supporting yet") network["enable_proxy"] = "not supporting yet"
val proxy = this.settings.getListSection("proxy") val proxy = this.settings.getListSection("proxy")
proxy.add("1.2.3.4:95") proxy.add("1.2.3.4:95")
proxy.add("1.2.3.4:100") proxy.add("1.2.3.4:100")
val worker = this.settings.getMapSection("worker") val worker = this.settings.getMapSection("worker")
worker.set("core_task_pool_worker_amount", 5) worker["core_task_pool_worker_amount"] = 5
val plugin = this.settings.getMapSection("plugin") val plugin = this.settings.getMapSection("plugin")
plugin.set("debug", false) plugin["debug"] = false
this.settings.save() this.settings.save()
logger.info("initialized; changing can be made in setting file: $setting") logger.info("initialized; changing can be made in setting file: $setting")
...@@ -142,7 +128,7 @@ object MiraiServer { ...@@ -142,7 +128,7 @@ object MiraiServer {
private fun reload() { private fun reload() {
this.enabled = true this.enabled = true
MiraiLogger.info(LoggerTextFormat.GREEN.toString() + "Server enabled; Welcome to Mirai") MiraiLogger.info(LoggerTextFormat.GREEN.toString() + "Server enabled; Welcome to Mirai")
MiraiLogger.info("Mirai Version=" + MiraiServer.MIRAI_VERSION + " QQ Version=" + MiraiServer.QQ_VERSION) MiraiLogger.info("Mirai Version=" + Mirai.VERSION)
MiraiLogger.info("Initializing [Bot]s") MiraiLogger.info("Initializing [Bot]s")
...@@ -189,7 +175,7 @@ object MiraiServer { ...@@ -189,7 +175,7 @@ object MiraiServer {
val strings = it.split("----").dropLastWhile { it.isEmpty() }.toTypedArray() val strings = it.split("----").dropLastWhile { it.isEmpty() }.toTypedArray()
val bot = Bot(BotAccount(strings[0].toLong(), strings[1]), Console()) val bot = Bot(BotAccount(strings[0].toLong(), strings[1]), Console())
if (runBlocking { bot.network.tryLogin(200).await() } === LoginState.SUCCESS) { if (runBlocking { bot.network.tryLogin(200) } === LoginState.SUCCESS) {
bot.green("Login succeed") bot.green("Login succeed")
return bot return bot
} }
......
apply plugin: "kotlin-multiplatform"
kotlin {
targets {
fromPreset(presets.jvm, "jvm")
}
jvm()
sourceSets {
commonMain {
dependencies {
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect
implementation rootProject.ext.kotlinCommon
implementation rootProject.ext.reflect
implementation rootProject.ext.coroutine
implementation rootProject.ext.kotlinJvm
}
}
jvmMain {
dependencies {
implementation rootProject.ext.kotlinJvm
implementation rootProject.ext.reflect
implementation rootProject.ext.coroutine
implementation 'org.yaml:snakeyaml:1.18'
implementation 'org.jsoup:jsoup:1.12.1'
implementation 'org.ini4j:ini4j:0.5.2'
}
}
all {
languageSettings.enableLanguageFeature("InlineClasses")
}
}
}
compileKotlinJvm{
}
configurations {
compileClasspath
}
/*
dependencies {
compile 'com.google.protobuf:protobuf-java:3.5.0'
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-M2'
compile 'io.netty:netty-all:4.1.38.Final'
compile 'net.java.dev.jna:jna:5.4.0'
compile 'org.apache.logging.log4j:log4j-core:2.12.1'
compile 'org.yaml:snakeyaml:1.18'
compile 'org.jetbrains.kotlin:kotlin-reflect:1.3.41'
compile 'org.jsoup:jsoup:1.12.1'
compile 'org.ini4j:ini4j:0.5.2'
}*/
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>mirai-core</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<parent>
<groupId>net.mamoe</groupId>
<artifactId>mirai</artifactId>
<version>1.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<!-- https://mvnrepository.com/artifact/jpcap/jpcap -->
<dependency>
<groupId>jpcap</groupId>
<artifactId>jpcap</artifactId>
<version>0.1.18-002</version>
<scope>system</scope>
<systemPath>../mirai-debug/lib/jpcap.jar</systemPath>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.ini4j</groupId>
<artifactId>ini4j</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>/src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-XXLanguage:+InlineClasses</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package net.mamoe.mirai.utils
//todo
\ No newline at end of file
package net.mamoe.mirai package net.mamoe.mirai
import kotlinx.coroutines.CompletableDeferred
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
...@@ -28,7 +27,7 @@ val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs ...@@ -28,7 +27,7 @@ val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs
//NetworkHandler //NetworkHandler
suspend fun Bot.sendPacket(packet: ClientPacket) = this.network.socket.sendPacket(packet) suspend fun Bot.sendPacket(packet: ClientPacket) = this.network.socket.sendPacket(packet)
fun Bot.login(touchingTimeoutMillis: Long = 200): CompletableDeferred<LoginState> = this.network.tryLogin() suspend fun Bot.login(touchingTimeoutMillis: Long = 200): LoginState = this.network.tryLogin()
//BotAccount //BotAccount
......
package net.mamoe.mirai
//expect fun s(): String
/**
* @author Him188moe
*/
object Mirai {
const val VERSION: String = "1.0.0"
}
\ No newline at end of file
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import kotlinx.coroutines.CompletableDeferred
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.defaults.MessageChain import net.mamoe.mirai.message.defaults.MessageChain
...@@ -24,7 +25,7 @@ abstract class Contact internal constructor(val bot: Bot, val number: Long) { ...@@ -24,7 +25,7 @@ abstract class Contact internal constructor(val bot: Bot, val number: Long) {
/** /**
* 上传图片 * 上传图片
*/ */
fun uploadImage(session: LoginSession, image: UnsolvedImage): CompletableFuture<Unit> { fun uploadImage(session: LoginSession, image: UnsolvedImage): CompletableDeferred<Unit> {
return image.upload(session, this) return image.upload(session, this)
} }
......
package net.mamoe.mirai.message.defaults package net.mamoe.mirai.message.defaults
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
...@@ -28,7 +29,7 @@ import javax.imageio.ImageIO ...@@ -28,7 +29,7 @@ import javax.imageio.ImageIO
class UnsolvedImage(filename: String, val image: BufferedImage) : Image(getImageId(filename)) { class UnsolvedImage(filename: String, val image: BufferedImage) : Image(getImageId(filename)) {
constructor(imageFile: File) : this(imageFile.name, ImageIO.read(imageFile)) constructor(imageFile: File) : this(imageFile.name, ImageIO.read(imageFile))
constructor(url: URL) : this(File(url.file)) constructor(url: URL) : this(File(url.file))
fun upload(session: LoginSession, contact: Contact): CompletableFuture<Unit> { fun upload(session: LoginSession, contact: Contact): CompletableDeferred<Unit> {
return session.expectPacket<ServerTryGetImageIDResponsePacket> { return session.expectPacket<ServerTryGetImageIDResponsePacket> {
toSend { ClientTryGetImageIDPacket(session.bot.account.qqNumber, session.sessionKey, contact.number, image) } toSend { ClientTryGetImageIDPacket(session.bot.account.qqNumber, session.sessionKey, contact.number, image) }
......
...@@ -12,7 +12,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.Packet ...@@ -12,7 +12,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket 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.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
import java.io.Closeable
/** /**
* Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务. * Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务.
...@@ -32,7 +31,7 @@ import java.io.Closeable ...@@ -32,7 +31,7 @@ import java.io.Closeable
* *
* @author Him188moe * @author Him188moe
*/ */
interface BotNetworkHandler : Closeable { interface BotNetworkHandler {
/** /**
* 网络层处理器. 用于编码/解码 [Packet], 发送/接受 [ByteArray] * 网络层处理器. 用于编码/解码 [Packet], 发送/接受 [ByteArray]
* *
...@@ -59,7 +58,7 @@ interface BotNetworkHandler : Closeable { ...@@ -59,7 +58,7 @@ interface BotNetworkHandler : Closeable {
* *
* @param touchingTimeoutMillis 连接每个服务器的 timeout * @param touchingTimeoutMillis 连接每个服务器的 timeout
*/ */
fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableDeferred<LoginState> suspend fun tryLogin(touchingTimeoutMillis: Long = 200): LoginState
/** /**
* 添加一个临时包处理器 * 添加一个临时包处理器
...@@ -68,5 +67,5 @@ interface BotNetworkHandler : Closeable { ...@@ -68,5 +67,5 @@ interface BotNetworkHandler : Closeable {
*/ */
fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>) fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>)
override fun close() fun close()
} }
\ No newline at end of file
package net.mamoe.mirai.network package net.mamoe.mirai.network
import kotlinx.coroutines.CompletableDeferred
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.tim.handler.DataPacketSocket import net.mamoe.mirai.network.protocol.tim.handler.DataPacketSocket
import net.mamoe.mirai.network.protocol.tim.handler.TemporaryPacketHandler import net.mamoe.mirai.network.protocol.tim.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.getGTK import net.mamoe.mirai.utils.getGTK
import java.util.concurrent.CompletableFuture
/** /**
* 登录会话. 当登录完成后, 客户端会拿到 sessionKey. * 登录会话. 当登录完成后, 客户端会拿到 sessionKey.
...@@ -58,10 +58,10 @@ class LoginSession( ...@@ -58,10 +58,10 @@ class LoginSession(
* @return future. 可进行超时处理 * @return future. 可进行超时处理
*/ */
@JvmSynthetic @JvmSynthetic
inline fun <reified P : ServerPacket> expectPacket(handlerTemporary: TemporaryPacketHandler<P>.() -> Unit): CompletableFuture<Unit> { inline fun <reified P : ServerPacket> expectPacket(handlerTemporary: TemporaryPacketHandler<P>.() -> Unit): CompletableDeferred<Unit> {
val future = CompletableFuture<Unit>() val deferred = CompletableDeferred<Unit>()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, future, this).also(handlerTemporary)) this.bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this).also(handlerTemporary))
return future return deferred
} }
/** /**
...@@ -82,12 +82,12 @@ class LoginSession( ...@@ -82,12 +82,12 @@ class LoginSession(
* @return future. 可进行超时处理 * @return future. 可进行超时处理
*/ */
@JvmSynthetic @JvmSynthetic
inline fun <reified P : ServerPacket> expectPacket(toSend: ClientPacket, noinline handler: suspend (P) -> Unit): CompletableFuture<Unit> { inline fun <reified P : ServerPacket> expectPacket(toSend: ClientPacket, noinline handler: suspend (P) -> Unit): CompletableDeferred<Unit> {
val future = CompletableFuture<Unit>() val deferred = CompletableDeferred<Unit>()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, future, this).also { this.bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this).also {
it.toSend(toSend) it.toSend(toSend)
it.onExpect(handler) it.onExpect(handler)
}) })
return future return deferred
} }
} }
\ No newline at end of file
...@@ -13,17 +13,12 @@ import net.mamoe.mirai.network.NetworkScope ...@@ -13,17 +13,12 @@ import net.mamoe.mirai.network.NetworkScope
import net.mamoe.mirai.network.protocol.tim.handler.* import net.mamoe.mirai.network.protocol.tim.handler.*
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.login.* import net.mamoe.mirai.network.protocol.tim.packet.login.*
import net.mamoe.mirai.task.MiraiThreadPool
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import java.io.Closeable
import java.io.File import java.io.File
import java.net.DatagramPacket import java.net.DatagramPacket
import java.net.DatagramSocket import java.net.DatagramSocket
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.util.* import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import javax.imageio.ImageIO import javax.imageio.ImageIO
/** /**
...@@ -53,28 +48,21 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler { ...@@ -53,28 +48,21 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
temporaryPacketHandlers.add(temporaryPacketHandler) temporaryPacketHandlers.add(temporaryPacketHandler)
} }
override fun tryLogin(touchingTimeoutMillis: Long): CompletableDeferred<LoginState> { override suspend fun tryLogin(touchingTimeoutMillis: Long): LoginState {
val ipQueue: LinkedList<String> = LinkedList(TIMProtocol.SERVER_IP) return loginInternal(touchingTimeoutMillis, LinkedList(TIMProtocol.SERVER_IP))
val future = CompletableDeferred<LoginState>() }
fun login() { private suspend fun loginInternal(touchingTimeoutMillis: Long, ipQueue: LinkedList<String>): LoginState {
this.socket.close() this.socket.close()
val ip = ipQueue.poll() val ip = ipQueue.poll() ?: return LoginState.UNKNOWN//所有服务器均返回 UNKNOWN
if (ip == null) {
future.complete(LoginState.UNKNOWN)//所有服务器均返回 UNKNOWN
return
}
this.socket.touch(ip, touchingTimeoutMillis).get().let { state -> return this.socket.touch(ip, touchingTimeoutMillis).await().let { state ->
if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) { if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) {
login()//超时或未知, 重试连接下一个服务器 loginInternal(touchingTimeoutMillis, ipQueue)//超时或未知, 重试连接下一个服务器
} else { } else {
future.complete(state) state
}
} }
} }
login()
return future
} }
//private | internal //private | internal
...@@ -97,7 +85,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler { ...@@ -97,7 +85,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
} }
internal inner class BotSocket : Closeable, DataPacketSocket { internal inner class BotSocket : DataPacketSocket {
override suspend fun distributePacket(packet: ServerPacket) { override suspend fun distributePacket(packet: ServerPacket) {
try { try {
packet.decode() packet.decode()
...@@ -156,7 +144,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler { ...@@ -156,7 +144,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
restartSocket() restartSocket()
} }
internal var loginFuture: CompletableFuture<LoginState>? = null internal var loginResult: CompletableDeferred<LoginState>? = null
@Synchronized @Synchronized
private fun restartSocket() { private fun restartSocket() {
...@@ -186,23 +174,23 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler { ...@@ -186,23 +174,23 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
/** /**
* Start network and touch the server * Start network and touch the server
*/ */
fun touch(serverAddress: String, timeoutMillis: Long): CompletableFuture<LoginState> { fun touch(serverAddress: String, timeoutMillis: Long): CompletableDeferred<LoginState> {
bot.info("Connecting server: $serverAddress") bot.info("Connecting server: $serverAddress")
if (this@TIMBotNetworkHandler::login.isInitialized) { if (this@TIMBotNetworkHandler::login.isInitialized) {
login.close() login.close()
} }
login = Login() login = Login()
this.loginFuture = CompletableFuture() this.loginResult = CompletableDeferred()
serverIP = serverAddress serverIP = serverAddress
bot.waitForPacket(ServerPacket::class, timeoutMillis) { bot.waitForPacket(ServerPacket::class, timeoutMillis) {
loginFuture!!.complete(LoginState.TIMEOUT) loginResult!!.complete(LoginState.TIMEOUT)
} }
runBlocking { runBlocking {
sendPacket(ClientTouchPacket(bot.account.qqNumber, serverIP)) sendPacket(ClientTouchPacket(bot.account.qqNumber, serverIP))
} }
return this.loginFuture!! return this.loginResult!!
} }
/** /**
...@@ -236,13 +224,14 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler { ...@@ -236,13 +224,14 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
override fun getOwner(): Bot = this@TIMBotNetworkHandler.bot override fun getOwner(): Bot = this@TIMBotNetworkHandler.bot
override fun close() { override fun close() {
this.socket?.close() this.socket?.close()
if (this.loginFuture != null) { if (this.loginResult != null) {
if (!this.loginFuture!!.isDone) { if (!this.loginResult!!.isCompleted) {
this.loginFuture!!.cancel(true) this.loginResult!!.cancel(CancellationException("socket closed"))
} }
this.loginFuture = null this.loginResult = null
} }
} }
...@@ -254,7 +243,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler { ...@@ -254,7 +243,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
/** /**
* 处理登录过程 * 处理登录过程
*/ */
inner class Login : Closeable { inner class Login {
private lateinit var token00BA: ByteArray private lateinit var token00BA: ByteArray
private lateinit var token0825: ByteArray//56 private lateinit var token0825: ByteArray//56
private var loginTime: Int = 0 private var loginTime: Int = 0
...@@ -269,7 +258,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler { ...@@ -269,7 +258,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
private var captchaSectionId: Int = 1 private var captchaSectionId: Int = 1
private var captchaCache: ByteArray? = byteArrayOf()//每次包只发一部分验证码来 private var captchaCache: ByteArray? = byteArrayOf()//每次包只发一部分验证码来
private var heartbeatFuture: ScheduledFuture<*>? = null private var heartbeatJob: Job? = null
suspend fun onPacketReceived(packet: ServerPacket) { suspend fun onPacketReceived(packet: ServerPacket) {
...@@ -289,7 +278,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler { ...@@ -289,7 +278,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
} }
is ServerLoginResponseFailedPacket -> { is ServerLoginResponseFailedPacket -> {
socket.loginFuture?.complete(packet.loginState) socket.loginResult?.complete(packet.loginState)
bot.close() bot.close()
return return
} }
...@@ -373,13 +362,13 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler { ...@@ -373,13 +362,13 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
is ServerSessionKeyResponsePacket -> { is ServerSessionKeyResponsePacket -> {
sessionKey = packet.sessionKey sessionKey = packet.sessionKey
heartbeatFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({
runBlocking {
socket.sendPacket(ClientHeartbeatPacket(bot.account.qqNumber, sessionKey))
}
}, 90000, 90000, TimeUnit.MILLISECONDS)
socket.loginFuture!!.complete(LoginState.SUCCESS) heartbeatJob = NetworkScope.launch {
delay(90000)
socket.sendPacket(ClientHeartbeatPacket(bot.account.qqNumber, sessionKey))
}
socket.loginResult!!.complete(LoginState.SUCCESS)
login.changeOnlineStatus(ClientLoginStatus.ONLINE) login.changeOnlineStatus(ClientLoginStatus.ONLINE)
} }
...@@ -420,12 +409,12 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler { ...@@ -420,12 +409,12 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
socket.sendPacket(ClientChangeOnlineStatusPacket(bot.account.qqNumber, sessionKey, status)) socket.sendPacket(ClientChangeOnlineStatusPacket(bot.account.qqNumber, sessionKey, status))
} }
override fun close() { fun close() {
this.captchaCache = null this.captchaCache = null
this.heartbeatFuture?.cancel(true) this.heartbeatJob?.cancel(CancellationException("handler closed"))
this.heartbeatFuture = null this.heartbeatJob = null
} }
} }
} }
...@@ -66,7 +66,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) { ...@@ -66,7 +66,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
session.sKey = packet.sKey session.sKey = packet.sKey
session.cookies = "uin=o" + session.bot.account.qqNumber + ";skey=" + session.sKey + ";" session.cookies = "uin=o" + session.bot.account.qqNumber + ";skey=" + session.sKey + ";"
sKeyRefresherFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({ sKeyRefresherFuture = MiraiThreadPool.instance.scheduleWithFixedDelay({
runBlocking { runBlocking {
session.socket.sendPacket(ClientSKeyRefreshmentRequestPacket(session.bot.account.qqNumber, session.sessionKey)) session.socket.sendPacket(ClientSKeyRefreshmentRequestPacket(session.bot.account.qqNumber, session.sessionKey))
} }
......
...@@ -6,7 +6,6 @@ import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler ...@@ -6,7 +6,6 @@ import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
import net.mamoe.mirai.network.LoginSession import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import java.io.Closeable
/** /**
* 网络接口. * 网络接口.
...@@ -15,7 +14,7 @@ import java.io.Closeable ...@@ -15,7 +14,7 @@ import java.io.Closeable
* *
* @author Him188moe * @author Him188moe
*/ */
interface DataPacketSocket : Closeable { interface DataPacketSocket {
fun getOwner(): Bot fun getOwner(): Bot
/** /**
...@@ -34,5 +33,5 @@ interface DataPacketSocket : Closeable { ...@@ -34,5 +33,5 @@ interface DataPacketSocket : Closeable {
fun isClosed(): Boolean fun isClosed(): Boolean
override fun close() fun close()
} }
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.handler package net.mamoe.mirai.network.protocol.tim.handler
import kotlinx.coroutines.CompletableDeferred
import net.mamoe.mirai.network.LoginSession import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
...@@ -21,7 +22,7 @@ import kotlin.reflect.KClass ...@@ -21,7 +22,7 @@ import kotlin.reflect.KClass
*/ */
open class TemporaryPacketHandler<P : ServerPacket>( open class TemporaryPacketHandler<P : ServerPacket>(
private val expectationClass: KClass<P>, private val expectationClass: KClass<P>,
private val future: CompletableFuture<Unit>, private val deferred: CompletableDeferred<Unit>,
private val fromSession: LoginSession private val fromSession: LoginSession
) { ) {
private lateinit var toSend: ClientPacket private lateinit var toSend: ClientPacket
...@@ -53,7 +54,7 @@ open class TemporaryPacketHandler<P : ServerPacket>( ...@@ -53,7 +54,7 @@ open class TemporaryPacketHandler<P : ServerPacket>(
if (expectationClass.isInstance(packet) && session === this.fromSession) { if (expectationClass.isInstance(packet) && session === this.fromSession) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
expect(packet as P) expect(packet as P)
future.complete(Unit) deferred.complete(Unit)
return true return true
} }
return false return false
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
package net.mamoe.mirai.network.protocol.tim.packet package net.mamoe.mirai.network.protocol.tim.packet
import lombok.Getter
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.PacketNameFormatter.adjustName import net.mamoe.mirai.network.protocol.tim.packet.PacketNameFormatter.adjustName
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
...@@ -16,7 +15,6 @@ import java.security.MessageDigest ...@@ -16,7 +15,6 @@ import java.security.MessageDigest
*/ */
abstract class ClientPacket : ByteArrayDataOutputStream(), Packet { abstract class ClientPacket : ByteArrayDataOutputStream(), Packet {
@Getter
val idHex: String val idHex: String
private var encoded: Boolean = false private var encoded: Boolean = false
......
...@@ -347,7 +347,7 @@ internal fun <P : ServerPacket> Bot.waitForPacket(packetClass: KClass<P>, timeou ...@@ -347,7 +347,7 @@ internal fun <P : ServerPacket> Bot.waitForPacket(packetClass: KClass<P>, timeou
} }
MiraiThreadPool.getInstance().submit { MiraiThreadPool.instance.submit {
val startingTime = System.currentTimeMillis() val startingTime = System.currentTimeMillis()
while (!got) { while (!got) {
if (System.currentTimeMillis() - startingTime > timeoutMillis) { if (System.currentTimeMillis() - startingTime > timeoutMillis) {
......
package net.mamoe.mirai.task
import java.util.concurrent.Callable
import java.util.concurrent.Future
import java.util.concurrent.atomic.AtomicInteger
import java.util.function.Consumer
import java.util.function.Predicate
/**
* @author NaturalHG
*/
/*
class MiraiTaskManager private constructor() {
private val pool: MiraiThreadPool
init {
this.pool = MiraiThreadPool()
}
/**
* 基础Future处理
*/
fun execute(runnable: Runnable) {
this.execute(runnable, MiraiTaskExceptionHandler.printing())
}
fun execute(runnable: Runnable, handler: MiraiTaskExceptionHandler) {
this.pool.execute {
try {
runnable.run()
} catch (e: Exception) {
handler.onHandle(e)
}
}
}
fun <D> submit(callable: Callable<D>): Future<D> {
return this.submit(callable, MiraiTaskExceptionHandler.printing())
}
fun <D> submit(callable: Callable<D>, handler: MiraiTaskExceptionHandler): Future<D> {
return this.pool.submit<D> {
try {
callable.call()
} catch (e: Throwable) {
handler.onHandle(e)
null
}
}
}
/**
* 异步任务
*/
fun <D> ansycTask(callable: Callable<D>, callback: Consumer<D>) {
this.ansycTask(callable, callback, MiraiTaskExceptionHandler.printing())
}
fun <D> ansycTask(callable: Callable<D>, callback: Consumer<D>, handler: MiraiTaskExceptionHandler) {
this.pool.execute {
try {
callback.accept(callable.call())
} catch (e: Throwable) {
handler.onHandle(e)
}
}
}
/**
* 定时任务
*/
fun repeatingTask(runnable: Runnable, intervalMillis: Long) {
this.repeatingTask(runnable, intervalMillis, MiraiTaskExceptionHandler.printing())
}
fun repeatingTask(runnable: Runnable, intervalMillis: Long, handler: MiraiTaskExceptionHandler) {
this.repeatingTask<Runnable>(runnable, intervalMillis, { a -> true }, handler)
}
fun repeatingTask(runnable: Runnable, intervalMillis: Long, times: Int) {
this.repeatingTask(runnable, intervalMillis, times, MiraiTaskExceptionHandler.printing())
}
fun repeatingTask(runnable: Runnable, intervalMillis: Long, times: Int, handler: MiraiTaskExceptionHandler) {
val integer = AtomicInteger(times - 1)
this.repeatingTask<Runnable>(
runnable, intervalMillis, { a -> integer.getAndDecrement() > 0 }, handler
)
}
fun <D : Runnable> repeatingTask(runnable: D, intervalMillis: Long, shouldContinue: Predicate<D>, handler: MiraiTaskExceptionHandler) {
Thread {
do {
this.pool.execute {
try {
runnable.run()
} catch (e: Exception) {
handler.onHandle(e)
}
}
try {
Thread.sleep(intervalMillis)
} catch (e: InterruptedException) {
e.printStackTrace()
}
} while (shouldContinue.test(runnable))
}.start()
}
fun deletingTask(runnable: Runnable, intervalMillis: Long) {
Thread {
try {
Thread.sleep(intervalMillis)
} catch (e: InterruptedException) {
e.printStackTrace()
}
this.pool.execute(runnable)
}.start()
}
companion object {
val instance = MiraiTaskManager()
}
}
*/
\ No newline at end of file
package net.mamoe.mirai.task
import net.mamoe.mirai.Mirai
import java.io.Closeable
import java.util.concurrent.ScheduledThreadPoolExecutor
/**
* @author NaturalHG
*/
class MiraiThreadPool internal constructor()/*super(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<>()
);*/ : ScheduledThreadPoolExecutor(10), Closeable {
override fun close() {
this.shutdown()
if (!this.isShutdown) {
this.shutdownNow()
}
}
companion object {
val instance = MiraiThreadPool()
@JvmStatic
fun main(args: Array<String>) {
println(Mirai.VERSION)
}
}
}
package net.mamoe.mirai.utils
import java.awt.*
import java.awt.image.BufferedImage
import java.util.ArrayList
import java.util.concurrent.Callable
import kotlin.math.max
import kotlin.math.min
/**
* Convert IMAGE into Chars that could shows in terminal
*
* @author NaturalHG
*/
class CharImageConverter @JvmOverloads constructor(
/**
* width should depends on the width of the terminal
*/
private var image: BufferedImage?, private val width: Int, private val ignoreRate: Double = 0.95) : Callable<String> {
override fun call(): String {
/*
* resize Image
* */
val newHeight = (this.image!!.getHeight() * (width.toDouble() / this.image!!.getWidth())).toInt()
val tmp = image!!.getScaledInstance(width, newHeight, Image.SCALE_SMOOTH)
val dimg = BufferedImage(width, newHeight, BufferedImage.TYPE_INT_ARGB)
val g2d = dimg.createGraphics()
g2d.drawImage(tmp, 0, 0, null)
this.image = dimg
val background = gray(image!!.getRGB(0, 0))
val builder = StringBuilder()
val lines = ArrayList<StringBuilder>(this.image!!.getHeight())
var minXPos = this.width
var maxXPos = 0
for (y in 0 until image!!.getHeight()) {
val builderLine = StringBuilder()
for (x in 0 until image!!.getWidth()) {
val gray = gray(image!!.getRGB(x, y))
if (grayCompare(gray, background)) {
builderLine.append(" ")
} else {
builderLine.append("#")
if (x < minXPos) {
minXPos = x
}
if (x > maxXPos) {
maxXPos = x
}
}
}
if (builderLine.toString().isBlank()) {
continue
}
lines.add(builderLine)
}
for (line in lines) {
builder.append(line.substring(minXPos, maxXPos)).append("\n")
}
return builder.toString()
}
private fun gray(rgb: Int): Int {
val R = rgb and 0xff0000 shr 16
val G = rgb and 0x00ff00 shr 8
val B = rgb and 0x0000ff
return (R * 30 + G * 59 + B * 11 + 50) / 100
}
fun grayCompare(g1: Int, g2: Int): Boolean {
return min(g1, g2).toDouble() / max(g1, g2) >= ignoreRate
}
}
package net.mamoe.mirai.utils
import java.awt.image.BufferedImage
/**
* @author NaturalHG
*/
object CharImageUtil {
@JvmOverloads
fun createCharImg(image: BufferedImage, sizeWeight: Int = 100, sizeHeight: Int = 20): String {
return CharImageConverter(image, sizeWeight).call()
}
}
\ No newline at end of file
package net.mamoe.mirai.utils; package net.mamoe.mirai.utils
/** /**
* QQ 在线状态 * QQ 在线状态
...@@ -6,17 +6,12 @@ package net.mamoe.mirai.utils; ...@@ -6,17 +6,12 @@ package net.mamoe.mirai.utils;
* @author Him188moe * @author Him188moe
* @see net.mamoe.mirai.network.protocol.tim.packet.login.ClientChangeOnlineStatusPacket * @see net.mamoe.mirai.network.protocol.tim.packet.login.ClientChangeOnlineStatusPacket
*/ */
public enum ClientLoginStatus { enum class ClientLoginStatus(
// TODO: 2019/8/31 add more ClientLoginStatus
val id: Int//1 ubyte
) {
/** /**
* 我在线上 * 我在线上
*/ */
ONLINE(0x0A); ONLINE(0x0A)
// TODO: 2019/8/31 add more ClientLoginStatus
public final int id;//1 ubyte
ClientLoginStatus(int id) {
this.id = id;
}
} }
package net.mamoe.mirai.utils; package net.mamoe.mirai.utils
import java.io.IOException
import java.io.IOException; import java.net.HttpURLConnection
import java.net.HttpURLConnection; import java.net.URL
import java.net.URL;
/** /**
* @author NaturalHG * @author NaturalHG
*/ */
public class ImageNetworkUtils { object ImageNetworkUtils {
public static boolean postImage(String uKeyHex, int fileSize, long botNumber, long groupCode, byte[] img) throws IOException { @Throws(IOException::class)
fun postImage(uKeyHex: String, fileSize: Int, botNumber: Long, groupCode: Long, img: ByteArray): Boolean {
//http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc&ukey=” + 删全部空 (ukey) + “&filesize=” + 到文本 (fileSize) + “&range=0&uin=” + g_uin + “&groupcode=” + Group //http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc&ukey=” + 删全部空 (ukey) + “&filesize=” + 到文本 (fileSize) + “&range=0&uin=” + g_uin + “&groupcode=” + Group
String builder = "http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc" + val builder = "http://htdata2.qq.com/cgi-bin/httpconn?htcmd=0x6ff0071&ver=5515&term=pc" +
"&ukey=" + uKeyHex.replace(" ", "") + "&ukey=" + uKeyHex.replace(" ", "") +
"&filezise=" + fileSize + "&filezise=" + fileSize +
"&range=" + "0" + "&range=" + "0" +
"&uin=" + botNumber + "&uin=" + botNumber +
"&groupcode=" + groupCode; "&groupcode=" + groupCode
HttpURLConnection conn = (HttpURLConnection) new URL(builder).openConnection(); val conn = URL(builder).openConnection() as HttpURLConnection
conn.setRequestProperty("User-Agent", "QQClient"); conn.setRequestProperty("User-Agent", "QQClient")
conn.setRequestProperty("Content-Length", "" + fileSize); conn.setRequestProperty("Content-Length", "" + fileSize)
conn.setRequestMethod("POST"); conn.requestMethod = "POST"
conn.setDoOutput(true); conn.doOutput = true
conn.getOutputStream().write(img); conn.outputStream.write(img)
conn.connect(); conn.connect()
return conn.getResponseCode() == 200; return conn.responseCode == 200
} }
} }
package net.mamoe.mirai.utils
/**
* @author NaturalHG
*/
enum class LoggerTextFormat(private val format: String) {
RESET("\u001b[0m"),
BLUE("\u001b[0;34m"),
BLACK("\u001b[0;30m"),
DARK_GREY("\u001b[1;30m"),
LIGHT_BLUE("\u001b[1;34m"),
GREEN("\u001b[0;32m"),
LIGHT_GTEEN("\u001b[1;32m"),
CYAN("\u001b[0;36m"),
LIGHT_CYAN("\u001b[1;36m"),
RED("\u001b[0;31m"),
LIGHT_RED("\u001b[1;31m"),
PURPLE("\u001b[0;35m"),
LIGHT_PURPLE("\u001b[1;35m"),
BROWN("\u001b[0;33m"),
YELLOW("\u001b[1;33m"),
LIGHT_GRAY("\u001b[0;37m"),
WHITE("\u001b[1;37m");
override fun toString(): String {
//if(MiraiServer.getInstance().isUnix()){
return format
// }
// return "";
}
}
package net.mamoe.mirai.utils; package net.mamoe.mirai.utils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.BiFunction; import java.util.function.BiFunction;
...@@ -54,7 +51,6 @@ public class MiraiSynchronizedLinkedHashMap<K, V> extends AbstractMap<K, V> { ...@@ -54,7 +51,6 @@ public class MiraiSynchronizedLinkedHashMap<K, V> extends AbstractMap<K, V> {
return sortedMap.get(key); return sortedMap.get(key);
} }
@Nullable
@Override @Override
public V put(K key, V value) { public V put(K key, V value) {
return sortedMap.put(key,value); return sortedMap.put(key,value);
...@@ -66,7 +62,7 @@ public class MiraiSynchronizedLinkedHashMap<K, V> extends AbstractMap<K, V> { ...@@ -66,7 +62,7 @@ public class MiraiSynchronizedLinkedHashMap<K, V> extends AbstractMap<K, V> {
} }
@Override @Override
public void putAll(@NotNull Map<? extends K, ? extends V> m) { public void putAll(Map<? extends K, ? extends V> m) {
sortedMap.putAll(m); sortedMap.putAll(m);
} }
...@@ -75,19 +71,16 @@ public class MiraiSynchronizedLinkedHashMap<K, V> extends AbstractMap<K, V> { ...@@ -75,19 +71,16 @@ public class MiraiSynchronizedLinkedHashMap<K, V> extends AbstractMap<K, V> {
sortedMap.clear(); sortedMap.clear();
} }
@NotNull
@Override @Override
public Set<K> keySet() { public Set<K> keySet() {
return sortedMap.keySet(); return sortedMap.keySet();
} }
@NotNull
@Override @Override
public Collection<V> values() { public Collection<V> values() {
return sortedMap.values(); return sortedMap.values();
} }
@NotNull
@Override @Override
public Set<Entry<K, V>> entrySet() { public Set<Entry<K, V>> entrySet() {
return sortedMap.entrySet(); return sortedMap.entrySet();
...@@ -113,7 +106,6 @@ public class MiraiSynchronizedLinkedHashMap<K, V> extends AbstractMap<K, V> { ...@@ -113,7 +106,6 @@ public class MiraiSynchronizedLinkedHashMap<K, V> extends AbstractMap<K, V> {
return this.sortedMap.replace(key,oldValue,newValue); return this.sortedMap.replace(key,oldValue,newValue);
} }
@Nullable
@Override @Override
public V replace(K key, V value) { public V replace(K key, V value) {
return this.sortedMap.replace(key,value); return this.sortedMap.replace(key,value);
...@@ -144,7 +136,6 @@ public class MiraiSynchronizedLinkedHashMap<K, V> extends AbstractMap<K, V> { ...@@ -144,7 +136,6 @@ public class MiraiSynchronizedLinkedHashMap<K, V> extends AbstractMap<K, V> {
return this.sortedMap.hashCode(); return this.sortedMap.hashCode();
} }
@Nullable
@Override @Override
public V putIfAbsent(K key, V value) { public V putIfAbsent(K key, V value) {
return this.sortedMap.putIfAbsent(key,value); return this.sortedMap.putIfAbsent(key,value);
......
package net.mamoe.mirai.utils; package net.mamoe.mirai.utils;
import org.jetbrains.annotations.NotNull;
import java.util.*; import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.IntFunction; import java.util.function.IntFunction;
...@@ -116,25 +114,21 @@ public class MiraiSynchronizedLinkedList<E> extends AbstractList<E> { ...@@ -116,25 +114,21 @@ public class MiraiSynchronizedLinkedList<E> extends AbstractList<E> {
return this.syncList.addAll(index, c); return this.syncList.addAll(index, c);
} }
@NotNull
@Override @Override
public Iterator<E> iterator() { public Iterator<E> iterator() {
return this.syncList.iterator(); return this.syncList.iterator();
} }
@NotNull
@Override @Override
public ListIterator<E> listIterator() { public ListIterator<E> listIterator() {
return this.syncList.listIterator(); return this.syncList.listIterator();
} }
@NotNull
@Override @Override
public ListIterator<E> listIterator(int index) { public ListIterator<E> listIterator(int index) {
return this.syncList.listIterator(index); return this.syncList.listIterator(index);
} }
@NotNull
@Override @Override
public List<E> subList(int fromIndex, int toIndex) { public List<E> subList(int fromIndex, int toIndex) {
return this.syncList.subList(fromIndex, toIndex); return this.syncList.subList(fromIndex, toIndex);
...@@ -155,16 +149,14 @@ public class MiraiSynchronizedLinkedList<E> extends AbstractList<E> { ...@@ -155,16 +149,14 @@ public class MiraiSynchronizedLinkedList<E> extends AbstractList<E> {
return this.syncList.contains(o); return this.syncList.contains(o);
} }
@NotNull
@Override @Override
public Object[] toArray() { public Object[] toArray() {
return this.syncList.toArray(); return this.syncList.toArray();
} }
@SuppressWarnings("SuspiciousToArrayCall") @SuppressWarnings("SuspiciousToArrayCall")
@NotNull
@Override @Override
public <T> T[] toArray(@NotNull T[] a) { public <T> T[] toArray(T[] a) {
return this.syncList.toArray(a); return this.syncList.toArray(a);
} }
...@@ -174,22 +166,22 @@ public class MiraiSynchronizedLinkedList<E> extends AbstractList<E> { ...@@ -174,22 +166,22 @@ public class MiraiSynchronizedLinkedList<E> extends AbstractList<E> {
} }
@Override @Override
public boolean containsAll(@NotNull Collection<?> c) { public boolean containsAll(Collection<?> c) {
return this.syncList.containsAll(c); return this.syncList.containsAll(c);
} }
@Override @Override
public boolean addAll(@NotNull Collection<? extends E> c) { public boolean addAll(Collection<? extends E> c) {
return this.syncList.addAll(c); return this.syncList.addAll(c);
} }
@Override @Override
public boolean removeAll(@NotNull Collection<?> c) { public boolean removeAll(Collection<?> c) {
return this.syncList.removeAll(c); return this.syncList.removeAll(c);
} }
@Override @Override
public boolean retainAll(@NotNull Collection<?> c) { public boolean retainAll(Collection<?> c) {
return this.syncList.retainAll(c); return this.syncList.retainAll(c);
} }
} }
...@@ -15,6 +15,7 @@ import java.util.zip.CRC32 ...@@ -15,6 +15,7 @@ import java.util.zip.CRC32
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import javax.imageio.ImageIO import javax.imageio.ImageIO
import kotlin.jvm.JvmSynthetic
/** /**
......
package net.mamoe.mirai.utils.config
import org.yaml.snakeyaml.DumperOptions
import org.yaml.snakeyaml.Yaml
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.nio.charset.Charset
import java.util.*
/**
* YAML-TYPE CONFIG
* Thread SAFE
*
* @author NaturalHG
*/
class MiraiConfig(private val root: File) : MiraiConfigSection<Any>(parse(Objects.requireNonNull(root))) {
@Synchronized
fun save() {
val dumperOptions = DumperOptions()
dumperOptions.defaultFlowStyle = DumperOptions.FlowStyle.BLOCK
val yaml = Yaml(dumperOptions)
val content = yaml.dump(this.sortedMap)
try {
ByteArrayInputStream(content.toByteArray()).transferTo(FileOutputStream(this.root))
} catch (e: IOException) {
e.printStackTrace()
}
}
companion object {
@Suppress("UNCHECKED_CAST")
private fun parse(file: File): MutableMap<String, Any>? {
/*
if (!file.toURI().getPath().contains(MiraiServer.getInstance().getParentFolder().getPath())) {
file = new File(MiraiServer.getInstance().getParentFolder().getPath(), file.getName());
}*/
if (!file.exists()) {
try {
if (!file.createNewFile()) {
return linkedMapOf()
}
} catch (e: IOException) {
e.printStackTrace()
return linkedMapOf()
}
}
val dumperOptions = DumperOptions()
dumperOptions.defaultFlowStyle = DumperOptions.FlowStyle.BLOCK
val yaml = Yaml(dumperOptions)
return yaml.loadAs<LinkedHashMap<*, *>>(file.readLines(Charset.defaultCharset()).joinToString("\n"), LinkedHashMap::class.java) as MutableMap<String, Any>?
}
}
}
package net.mamoe.mirai.utils.config; package net.mamoe.mirai.utils.config;
import net.mamoe.mirai.utils.MiraiSynchronizedLinkedHashMap; import net.mamoe.mirai.utils.MiraiSynchronizedLinkedHashMap;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
...@@ -138,7 +137,6 @@ public class MiraiConfigSection<T> extends MiraiSynchronizedLinkedHashMap<String ...@@ -138,7 +137,6 @@ public class MiraiConfigSection<T> extends MiraiSynchronizedLinkedHashMap<String
return result.toString(); return result.toString();
} }
@Nullable
@Override @Override
public T put(String key, T value) { public T put(String key, T value) {
return super.put(key, value); return super.put(key, value);
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment