├── .gitignore ├── .gradle ├── buildOutputCleanup │ ├── buildOutputCleanup.lock │ └── cache.properties ├── checksums │ └── checksums.lock └── vcs-1 │ └── gc.properties ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── copyright │ ├── AGPL.xml │ └── profiles_settings.xml ├── gradle.xml ├── jarRepositories.xml ├── kotlinc.xml ├── libraries-with-intellij-classes.xml ├── misc.xml ├── uiDesigner.xml ├── vcs.xml └── workspace.xml ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libs └── rhino-1.7.13.jar ├── out └── production │ └── resources │ └── strings.properties ├── settings.gradle.kts └── src └── main ├── kotlin └── io │ └── github │ └── rosemoe │ └── miraiPlugin │ ├── ImageSourceConfig.kt │ ├── ImageStorage.kt │ ├── MessageStates.kt │ ├── RosemoePlugin.kt │ ├── RosemoePluginConfig.kt │ ├── Version.kt │ ├── command │ ├── Command.kt │ ├── CommandDispatcher.kt │ ├── CommandPermissionChecker.kt │ ├── CommandTriggerPath.kt │ ├── MsgEvent.kt │ ├── OptionStates.kt │ └── description.kt │ ├── commands │ ├── Blocklist.kt │ ├── Help.kt │ ├── Pixiv.kt │ ├── Settings.kt │ ├── Setu.kt │ └── Sources.kt │ ├── gifmaker │ ├── Color.kt │ ├── GifEncoder.kt │ ├── LZWEncoder.kt │ └── NeuQuant.kt │ ├── modules │ ├── AtReply.kt │ ├── PetPet.kt │ ├── Pixiv.kt │ ├── Repeat.kt │ └── RepeatBreaking.kt │ └── utils │ ├── PluginFiles.kt │ ├── PluginRecallWorker.kt │ ├── ScriptMethods.kt │ └── Utils.kt └── resources ├── META-INF └── services │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin └── strings.properties /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | .gradle/ 3 | build/ 4 | release/ 5 | 6 | # Local configuration file (sdk path, etc) 7 | local.properties 8 | 9 | # Log Files 10 | *.log 11 | 12 | # IntelliJ 13 | *.iml 14 | .idea/workspace.xml 15 | .idea/tasks.xml 16 | .idea/gradle.xml 17 | .idea/assetWizardSettings.xml 18 | .idea/dictionaries 19 | .idea/libraries 20 | 21 | 22 | # Keystore files 23 | # Uncomment the following lines if you do not want to check your keystore files in. 24 | #*.jks 25 | #*.keystore 26 | 27 | # Version control 28 | vcs.xml 29 | 30 | # lint 31 | lint/intermediates/ 32 | lint/generated/ 33 | lint/outputs/ 34 | lint/tmp/ 35 | # lint/reports/ 36 | -------------------------------------------------------------------------------- /.gradle/buildOutputCleanup/buildOutputCleanup.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rosemoe/BotPlugin/d4bf56f6799d0b2c3f5230528d444e7d77a302fb/.gradle/buildOutputCleanup/buildOutputCleanup.lock -------------------------------------------------------------------------------- /.gradle/buildOutputCleanup/cache.properties: -------------------------------------------------------------------------------- 1 | #Thu Dec 01 12:16:31 CST 2022 2 | gradle.version=7.5 3 | -------------------------------------------------------------------------------- /.gradle/checksums/checksums.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rosemoe/BotPlugin/d4bf56f6799d0b2c3f5230528d444e7d77a302fb/.gradle/checksums/checksums.lock -------------------------------------------------------------------------------- /.gradle/vcs-1/gc.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rosemoe/BotPlugin/d4bf56f6799d0b2c3f5230528d444e7d77a302fb/.gradle/vcs-1/gc.properties -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/AGPL.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | 44 | 45 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/libraries-with-intellij-classes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 64 | 65 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 324 | 325 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BotPlugin 2 | 一个运行于[Mirai Console](https://github.com/mamoe/mirai-console)的插件 3 | A plugin for [Mirai Console](https://github.com/mamoe/mirai-console) with image sender,pixiv querying and more. 4 | ## 功能 5 | * 发送本地图库的圖片(请手动下载赫图,另提供指令sendImage一次发送多张)(发送带有'图'和'来'的消息或者色图来图片(不知道这图我也没办法,你可以用图片ID发出去)) 6 | * 发送在线的Json源图片 7 | * 发送在线的Pixiv的圖片(允许设置代理,说得好像不设置代理谁进得去呢?) 8 | * 被At时复读,并且把At的对象换成对方(此外还支持颠倒消息顺序,随机旋转消息图片) 9 | * 在群聊中更新某些配置(带有用户控制(废话)) 10 | * 设置群聊黑名单(注意settings和blacklist指令全局可用,黑名单群settings指令只回復manager) 11 | * 禁言,取消禁言,加群,退群提示(注意:有臭味 12 | * PetPet图片生成(群聊被戳一戳时,请务必为账号使用ANDROID_PHONE协议以便正常使用) 13 | * 插件管理员执行Javascript脚本(可用变量:bot,event,dlg,dlg请查看ScriptMethods.kt获取具体用法) 14 | * 随机复读 15 | * 没了 16 | ## 特色 17 | * 单线程撤回发出的本地图片(防止请求过于频繁被服务器拒绝) 18 | * CommandDispatcher(?蜜汁操作,指令派发) 19 | **在本仓库的Releases中可以直接下载编译好的jar文件! 如果不是需要研究,你可以直接下载** 20 | ## 简单使用 21 | - 下载Release里发布的jar. 22 | - [启动Mirai-Console](https://github.com/mamoe/mirai-console/blob/master/docs/Run.md) 23 | - 复制jar到Console工作目录下的plugins里 24 | - 见下方'开始使用'栏目 25 | ### 当前项目环境: 26 | * Mirai 2.6.5 27 | * Kotlin 1.5.10 28 | * OpenJdk 14 29 | * Intellij IDEA 2021.1.2 30 | **注意! 在编译本项目之前,请留意删除gradle.properties中的网络代理设置** 31 | ## 开始使用 32 | 建议使用 Java 11或者更高版本的Java 33 | 先带着插件运行一次Mirai Console,然后停止 34 | 在`mirai文件夹/config/RosemoeBotPlugin/PluginConfig.yml`中把 35 | ```yml 36 | managers: [] 37 | ``` 38 | 修改为 39 | ```yml 40 | managers: 41 | - 你的QQ 42 | ``` 43 | 当然也可以添加多个Manager,比如 44 | ```yml 45 | managers: 46 | - 114514 47 | - 1919810 48 | - 12345678 49 | ``` 50 | 然后重新运行Mirai Console 51 | ## 群聊指令表 52 | 非常建议您先读完下面的指令表再使用 53 | ### 设置部分 54 | 只有Plugin Manager(不是Bot Manager)才能使用这些指令! 55 | #### 全局设置 56 | ```Bash 57 | /settings set recallDelay <时间> 58 | /settings set recallInterval <时间> 59 | /settings set prefix <指令前缀,默认'/'> 60 | /settings set repeatFactor <概率的小数> 61 | /settings reload 62 | /settings reloadq 63 | /settings get recallDelay 64 | /settings get recallInterval 65 | /settings get prefix 66 | /settings get repeatFactor 67 | /settings enable <功能> 68 | /settings disable <功能> 69 | ``` 70 | reloadBase只刷新配置不重新建立图片索引,算是轻重载 71 | #### 图片源设置 72 | 图片源保存在image_sources.yml中 73 | ```Bash 74 | 添加一个路径: 75 | /sources path <名称> <路径> 76 | 77 | 添加一个在线Json图源 78 | /sources json <名称> <网址> <数据路径> 79 | 对应的网址要返回一个Json文本,其中通过数据路径可以到达url元素 80 | 比如返回下面这段Json: 81 | {"code":1,"msg":"ok","data":"http:\/\/test.xxx.com\/large\/a15b4afegy1fmvjv7pshlj21hc0u0e0s.jpg"} 82 | 需要设置的数据路径是 data 83 | 对于下面这段Json: 84 | {"code":0,"msg":"","quota":8,"quota_min_ttl":7029,"count":1,"data":[{"pid":61732396,"p":0,"uid":946272,"title":"カンナ","author":"Aile\/エル","url":"https:\/\/i.pixiv.cat\/img-original\/img\/2017\/03\/04\/00\/00\/01\/61732396_p0.png","r18":false,"width":583,"height":650,"tags":["カンナカムイ(小林さんちのメイドラゴン)","康娜卡姆依(小林家的龙女仆)","カンナ","康娜","カンナカムイ","康娜卡姆依","小林さんちのメイドラゴン","小林家的龙女仆","尻神様","尻神样","竜娘","龙娘","マジやばくね","that's wicked","高品質パンツ","高品质内裤","魅惑のふともも","魅惑的大腿"]}]} 85 | 需要设置的数据路径是 data\0\url 86 | 87 | 删除一个源 88 | /sources remove <名称> 89 | 90 | 刷新源列表 91 | 对源进行操作时不会立即生效,使用settings reload会导致设置文件被覆盖 92 | 可以使用这个方法来在修改图源列表后刷新图源 93 | /sources refresh 94 | ``` 95 | 另外,你也可以使用脚本手动完成获取图片的逻辑。至于如何配置使用,请研究ImageSource.kt(懒得写指令了23333) 96 | #### 功能名称表 97 | ```Kotlin 98 | val allowedModuleName = listOf( 99 | "ImageSender", 100 | "Pixiv", 101 | "BatchImg", 102 | "AtReply", 103 | "Welcome", 104 | "MuteTip", 105 | "ReverseAtReply", 106 | "ReverseAtReplyImage", 107 | "PetPet", 108 | "Repeat", 109 | "Help" 110 | ) 111 | ``` 112 | ### Pixiv 113 | ```Bash 114 | 查看画作信息和预览图 115 | /pixiv illust <画作ID> 116 | 查看指定画作的指定P的大图 117 | /pixiv illust <画作ID> <图片索引> 118 | ``` 119 | R18画作将不会发送图片 120 | ### 本地图片 121 | ```Bash 122 | /sendImage <图片数目> 123 | ``` 124 | ### Ping 125 | ```Bash 126 | /ping 后面和操作系统一样 127 | ``` 128 | ### IP获取 129 | ```Bash 130 | /ipList <网址> 131 | /ipList4 <网址> 132 | /ipList6 <网址> 133 | ``` 134 | ### 黑名单 135 | ```Bash 136 | /blocklist add <群号或this> 137 | /blocklist remove <群号或this> 138 | /blocklist list <页码,可选,每页15个> 139 | ``` 140 | ## Console指令表 141 | 咕了,还没写呢 142 | ## 设置Pixiv代理 143 | * `proxyEnabled` 配置是否启用代理 144 | * `proxyType` 配置代理类型,必须是socks或者http其中一种,填写其它默认socks 145 | * `proxyAddress` 配置代理地址,如`127.0.0.1` 146 | * `proxyPort` 配置代理端口,如`1080` 147 | 这些代理设置仅用于Pixiv网路连接,不作它用 148 | 实现上使用了Java的Proxy类 149 | ## 示例PluginConfig.yml 150 | ```yml 151 | managers: 152 | - 1145141919 153 | - 8101145141 154 | states: 155 | ImageSender: true 156 | commandPrefix: '/' 157 | darkListGroups: 158 | - 122344646 159 | - 464544644 160 | imageRecallDelay: 30000 161 | recallMinPeriod: 180 162 | maxImageRequestCount: 16 163 | proxyEnabled: true 164 | proxyType: socks 165 | proxyAddress: 127.0.0.1 166 | proxyPort: 1080 167 | allowR18ImageInPixiv: false 168 | repeatFactor: 0.05 169 | ``` 170 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * RosemoeBotPlugin 3 | * Copyright (C) 2020-2021 Rosemoe 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | plugins { 21 | val kotlinVersion = "1.7.10" 22 | kotlin("jvm") version kotlinVersion 23 | kotlin("plugin.serialization") version kotlinVersion 24 | java 25 | id("net.mamoe.mirai-console") version "2.13.0" 26 | } 27 | 28 | group = "io.github.Rosemoe" 29 | version = "2.4.2" 30 | 31 | repositories { 32 | mavenLocal() 33 | jcenter() 34 | mavenCentral() 35 | maven("https://dl.bintray.com/kotlin/kotlin-eap") 36 | } 37 | 38 | dependencies { 39 | compileOnly("net.mamoe:mirai-core-all:2.13.0") 40 | compileOnly(kotlin("stdlib-jdk8")) 41 | implementation("org.json:org.json:2.0") 42 | implementation(files("libs/rhino-1.7.13.jar")) 43 | testImplementation(kotlin("stdlib-jdk8")) 44 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # RosemoeBotPlugin 3 | # Copyright (C) 2020-2021 Rosemoe 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published 7 | # by the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | kotlin.code.style=official 20 | systemProp.http.proxyHost=localhost 21 | systemProp.http.proxyPort=8888 22 | systemProp.https.proxyHost=localhost 23 | systemProp.https.proxyPort=8888 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rosemoe/BotPlugin/d4bf56f6799d0b2c3f5230528d444e7d77a302fb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # RosemoeBotPlugin 5 | # Copyright (C) 2020-2021 Rosemoe 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU Affero General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU Affero General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Affero General Public License 18 | # along with this program. If not, see . 19 | # 20 | 21 | ############################################################################## 22 | ## 23 | ## Gradle start up script for UN*X 24 | ## 25 | ############################################################################## 26 | 27 | # Attempt to set APP_HOME 28 | # Resolve links: $0 may be a link 29 | PRG="$0" 30 | # Need this for relative symlinks. 31 | while [ -h "$PRG" ] ; do 32 | ls=`ls -ld "$PRG"` 33 | link=`expr "$ls" : '.*-> \(.*\)$'` 34 | if expr "$link" : '/.*' > /dev/null; then 35 | PRG="$link" 36 | else 37 | PRG=`dirname "$PRG"`"/$link" 38 | fi 39 | done 40 | SAVED="`pwd`" 41 | cd "`dirname \"$PRG\"`/" >/dev/null 42 | APP_HOME="`pwd -P`" 43 | cd "$SAVED" >/dev/null 44 | 45 | APP_NAME="Gradle" 46 | APP_BASE_NAME=`basename "$0"` 47 | 48 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 49 | DEFAULT_JVM_OPTS='"-Xmx64m"' 50 | 51 | # Use the maximum available, or set MAX_FD != -1 to use that value. 52 | MAX_FD="maximum" 53 | 54 | warn () { 55 | echo "$*" 56 | } 57 | 58 | die () { 59 | echo 60 | echo "$*" 61 | echo 62 | exit 1 63 | } 64 | 65 | # OS specific support (must be 'true' or 'false'). 66 | cygwin=false 67 | msys=false 68 | darwin=false 69 | nonstop=false 70 | case "`uname`" in 71 | CYGWIN* ) 72 | cygwin=true 73 | ;; 74 | Darwin* ) 75 | darwin=true 76 | ;; 77 | MINGW* ) 78 | msys=true 79 | ;; 80 | NONSTOP* ) 81 | nonstop=true 82 | ;; 83 | esac 84 | 85 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 86 | 87 | # Determine the Java command to use to start the JVM. 88 | if [ -n "$JAVA_HOME" ] ; then 89 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 90 | # IBM's JDK on AIX uses strange locations for the executables 91 | JAVACMD="$JAVA_HOME/jre/sh/java" 92 | else 93 | JAVACMD="$JAVA_HOME/bin/java" 94 | fi 95 | if [ ! -x "$JAVACMD" ] ; then 96 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 97 | 98 | Please set the JAVA_HOME variable in your environment to match the 99 | location of your Java installation." 100 | fi 101 | else 102 | JAVACMD="java" 103 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 104 | 105 | Please set the JAVA_HOME variable in your environment to match the 106 | location of your Java installation." 107 | fi 108 | 109 | # Increase the maximum file descriptors if we can. 110 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 111 | MAX_FD_LIMIT=`ulimit -H -n` 112 | if [ $? -eq 0 ] ; then 113 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 114 | MAX_FD="$MAX_FD_LIMIT" 115 | fi 116 | ulimit -n $MAX_FD 117 | if [ $? -ne 0 ] ; then 118 | warn "Could not set maximum file descriptor limit: $MAX_FD" 119 | fi 120 | else 121 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 122 | fi 123 | fi 124 | 125 | # For Darwin, add options to specify how the application appears in the dock 126 | if $darwin; then 127 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 128 | fi 129 | 130 | # For Cygwin, switch paths to Windows format before running java 131 | if $cygwin ; then 132 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 133 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=$((i+1)) 160 | done 161 | case $i in 162 | (0) set -- ;; 163 | (1) set -- "$args0" ;; 164 | (2) set -- "$args0" "$args1" ;; 165 | (3) set -- "$args0" "$args1" "$args2" ;; 166 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=$(save "$@") 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 186 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 187 | cd "$(dirname "$0")" 188 | fi 189 | 190 | exec "$JAVACMD" "$@" 191 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /libs/rhino-1.7.13.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rosemoe/BotPlugin/d4bf56f6799d0b2c3f5230528d444e7d77a302fb/libs/rhino-1.7.13.jar -------------------------------------------------------------------------------- /out/production/resources/strings.properties: -------------------------------------------------------------------------------- 1 | # 2 | # RosemoeBotPlugin 3 | # Copyright (C) 2020-2021 Rosemoe 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published 7 | # by the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | settings.pluginName=BotPlugin设置 20 | settings.managers=管理员控制 21 | settings.moduleStates=功能开关 22 | settings.blacklist=黑名单 23 | settings.proxy=代理设置 24 | settings.other=杂项 -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | gradlePluginPortal() 5 | jcenter() 6 | maven("https://dl.bintray.com/kotlin/kotlin-eap") 7 | } 8 | } 9 | 10 | rootProject.name = "RosemoeBotPlugin" 11 | 12 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/rosemoe/miraiPlugin/ImageSourceConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * RosemoeBotPlugin 3 | * Copyright (C) 2020-2021 Rosemoe 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package io.github.rosemoe.miraiPlugin 20 | 21 | import net.mamoe.mirai.console.data.AutoSavePluginConfig 22 | import net.mamoe.mirai.console.data.value 23 | 24 | object ImageSourceConfig : AutoSavePluginConfig("images_storages") { 25 | 26 | var sources : MutableList by value() 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/rosemoe/miraiPlugin/ImageStorage.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * RosemoeBotPlugin 3 | * Copyright (C) 2020-2021 Rosemoe 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package io.github.rosemoe.miraiPlugin 20 | 21 | import kotlinx.serialization.* 22 | import kotlinx.serialization.json.Json 23 | import kotlinx.serialization.modules.SerializersModule 24 | import kotlinx.serialization.modules.polymorphic 25 | import kotlinx.serialization.modules.subclass 26 | import net.mamoe.mirai.utils.ExternalResource 27 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 28 | import org.json.JSONArray 29 | import org.json.JSONObject 30 | import org.mozilla.javascript.Context 31 | import org.mozilla.javascript.ScriptableObject 32 | import java.io.File 33 | import java.lang.Exception 34 | import java.lang.IllegalArgumentException 35 | import java.net.URL 36 | import java.util.concurrent.locks.ReentrantReadWriteLock 37 | import kotlin.random.Random 38 | 39 | private val JsonSerialize = Json { 40 | ignoreUnknownKeys = true 41 | classDiscriminator = "type" 42 | serializersModule = SerializersModule { 43 | polymorphic(ImageStorage::class) { 44 | subclass(LocalImageStorage::class) 45 | subclass(OnlineJsonImageStorage::class) 46 | subclass(ScriptProxyImageStorage::class) 47 | } 48 | } 49 | } 50 | 51 | fun ImageStorage.serializeStorage() : String { 52 | return JsonSerialize.encodeToString(this) 53 | } 54 | 55 | fun deserializeStorage(data: String) : ImageStorage { 56 | return JsonSerialize.decodeFromString(data) 57 | } 58 | 59 | /** 60 | * Image Storage for sending images 61 | */ 62 | @Serializable 63 | sealed class ImageStorage { 64 | 65 | @Required 66 | var storageName: String = "unnamed" 67 | 68 | /** 69 | * Get a random image from the source 70 | */ 71 | open fun obtainImage(): ExternalResource? { 72 | return null 73 | } 74 | 75 | /** 76 | * initialize 77 | */ 78 | open fun init() { 79 | 80 | } 81 | 82 | } 83 | 84 | @Serializable 85 | @SerialName("local") 86 | class LocalImageStorage(private val path: String) : ImageStorage() { 87 | 88 | @Transient 89 | private val file = File(path) 90 | @Transient 91 | private val images = ArrayList() 92 | @Transient 93 | private val lock = ReentrantReadWriteLock() 94 | @Transient 95 | private val random = Random(System.nanoTime() + System.currentTimeMillis()) 96 | 97 | init { 98 | check(file.exists()) { "File does not exist" } 99 | } 100 | 101 | private fun File.isImageFile(): Boolean { 102 | val lowerCase = name.lowercase() 103 | return lowerCase.endsWith(".jpeg") || lowerCase.endsWith(".jpg") || lowerCase.endsWith(".png") || lowerCase.endsWith(".bmp") || lowerCase.endsWith( 104 | ".webp" 105 | ) 106 | } 107 | 108 | override fun init() { 109 | lock.lockWrite() 110 | try { 111 | images.clear() 112 | fun search(file: File) { 113 | if (file.isFile && file.isImageFile()) { 114 | images.add(file) 115 | } else { 116 | file.listFiles()?.forEach { 117 | search(it) 118 | } 119 | } 120 | } 121 | search(file) 122 | } finally { 123 | lock.unlockWrite() 124 | } 125 | } 126 | 127 | override fun obtainImage(): ExternalResource? { 128 | lock.lockRead() 129 | try { 130 | if (images.isEmpty()) { 131 | return null 132 | } 133 | return images[random.nextInt(0, images.size)].toExternalResource() 134 | } finally { 135 | lock.unlockRead() 136 | } 137 | } 138 | 139 | } 140 | 141 | @Serializable 142 | @SerialName("json") 143 | class OnlineJsonImageStorage(private val requestUrl: String, private val jsonPathForUrl: String) : ImageStorage() { 144 | 145 | @Transient 146 | private val pathList = jsonPathForUrl.split("\\") 147 | 148 | override fun obtainImage(): ExternalResource { 149 | var obj: Any = JSONObject(getWebpageSource(requestUrl)) 150 | for (name in pathList) { 151 | if (obj is JSONObject) { 152 | obj = obj.get(name) 153 | } else if (obj is JSONArray) { 154 | obj = obj.get(name.toInt()) 155 | } else if (obj is String) { 156 | break 157 | } else { 158 | throw IllegalArgumentException("Unable to extract content from $obj") 159 | } 160 | } 161 | var url = obj.toString() 162 | if (url.startsWith("//")) { 163 | url = "https:$url" 164 | } 165 | return URL(url).openConnection().getInputStream().toExternalResource() 166 | } 167 | 168 | override fun init() { 169 | // Empty 170 | } 171 | 172 | } 173 | 174 | @Serializable 175 | @SerialName("proxy") 176 | class ScriptProxyImageStorage(private val script: String) : ImageStorage() { 177 | 178 | override fun obtainImage(): ExternalResource? { 179 | val context = Context.enter() 180 | try { 181 | val script = context.compileString(script, "", 1, null) 182 | val scope = context.initStandardObjects() 183 | fun setJsObject(name: String, value: Any?) { 184 | val jsObj = Context.javaToJS(value, scope) 185 | ScriptableObject.putProperty(scope, name, jsObj) 186 | } 187 | 188 | val result = script.exec(context, scope) 189 | if (result == null) { 190 | logWarning("ImageStorage named '${storageName}' attempted to execute its script but got null result") 191 | return null 192 | } else if (result is ExternalResource) { 193 | return result 194 | } else if (result is String) { 195 | return URL(result).openConnection().getInputStream().toExternalResource() 196 | } 197 | logWarning("ImageStorage named '${storageName}' attempted to execute its script but got unexpected result type ${result.javaClass.name}") 198 | return null 199 | } catch (e: Exception) { 200 | logError("ImageStorage named '${storageName}' failed to execute its script", e) 201 | } finally { 202 | Context.exit() 203 | } 204 | return null 205 | } 206 | 207 | override fun init() { 208 | // Empty 209 | } 210 | 211 | } 212 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/rosemoe/miraiPlugin/MessageStates.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * RosemoeBotPlugin 3 | * Copyright (C) 2020-2021 Rosemoe 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package io.github.rosemoe.miraiPlugin 20 | 21 | import kotlinx.coroutines.CoroutineScope 22 | import kotlinx.coroutines.Job 23 | import kotlinx.coroutines.delay 24 | import kotlinx.coroutines.launch 25 | import net.mamoe.mirai.event.events.GroupMessageEvent 26 | import java.util.concurrent.ConcurrentHashMap 27 | import java.util.concurrent.locks.ReentrantReadWriteLock 28 | 29 | class MessageStates { 30 | 31 | private val lock = ReentrantReadWriteLock() 32 | 33 | private val mapGroup = ConcurrentHashMap() 34 | 35 | class GroupMessages { 36 | 37 | private val savedSigns = HashMap() 38 | 39 | fun handleForSign(sign: Long): Boolean { 40 | synchronized(this) { 41 | if (savedSigns.containsKey(sign)) { 42 | return false 43 | } 44 | savedSigns.put(sign, true) 45 | return true 46 | } 47 | } 48 | 49 | fun clear() { 50 | synchronized(this) { 51 | savedSigns.clear() 52 | } 53 | } 54 | 55 | } 56 | 57 | fun GroupMessageEvent.getSign(): Long { 58 | return sender.id.shl(29).or(source.time.toLong()) 59 | } 60 | 61 | fun handle(event: GroupMessageEvent): Boolean { 62 | lock.lockRead() 63 | try { 64 | val groupMessages = mapGroup.computeIfAbsent(event.group.id) { 65 | GroupMessages() 66 | } 67 | return groupMessages.handleForSign(event.getSign()) 68 | } finally { 69 | lock.unlockRead() 70 | } 71 | } 72 | 73 | fun clear() { 74 | lock.lockWrite() 75 | try { 76 | mapGroup.forEachValue(2) { 77 | it.clear() 78 | } 79 | } finally { 80 | lock.unlockWrite() 81 | } 82 | } 83 | 84 | fun launchClearer(scope: CoroutineScope): Job { 85 | return scope.launch { 86 | while (true) { 87 | clear() 88 | delay(30 * 1000) //30 seconds 89 | } 90 | } 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/rosemoe/miraiPlugin/RosemoePlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * RosemoeBotPlugin 3 | * Copyright (C) 2020-2021 Rosemoe 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package io.github.rosemoe.miraiPlugin 20 | 21 | import io.github.rosemoe.miraiPlugin.command.CommandPermissionChecker 22 | import io.github.rosemoe.miraiPlugin.command.Command 23 | import io.github.rosemoe.miraiPlugin.command.CommandDispatcher 24 | import io.github.rosemoe.miraiPlugin.command.MsgEvent 25 | import io.github.rosemoe.miraiPlugin.commands.* 26 | import io.github.rosemoe.miraiPlugin.commands.Setu.initializeImageList 27 | import io.github.rosemoe.miraiPlugin.commands.Setu.sendImageForEvent 28 | import io.github.rosemoe.miraiPlugin.modules.handleGpMessageForRepeating 29 | import io.github.rosemoe.miraiPlugin.utils.startRecallManager 30 | import kotlinx.coroutines.launch 31 | import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin 32 | import net.mamoe.mirai.console.plugin.jvm.reloadPluginConfig 33 | import net.mamoe.mirai.contact.Group 34 | import net.mamoe.mirai.contact.MemberPermission 35 | import net.mamoe.mirai.contact.User 36 | import net.mamoe.mirai.contact.nameCardOrNick 37 | import net.mamoe.mirai.event.EventHandler 38 | import net.mamoe.mirai.event.ListenerHost 39 | import net.mamoe.mirai.event.events.* 40 | import net.mamoe.mirai.event.globalEventChannel 41 | import net.mamoe.mirai.message.MessageReceipt 42 | import net.mamoe.mirai.message.data.At 43 | import net.mamoe.mirai.message.data.Message 44 | import net.mamoe.mirai.message.data.PlainText 45 | import net.mamoe.mirai.message.data.messageChainOf 46 | 47 | object RosemoePlugin : ListenerHost, KotlinPlugin( 48 | net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription( 49 | id = "io.github.rosemoe.miraiPlugin.RosemoePlugin", 50 | version = pluginVersion 51 | ) { 52 | name("RosemoeBotPlugin") 53 | author("Rosemoe") 54 | } 55 | ) , CommandPermissionChecker { 56 | /** 57 | * Constant fields 58 | */ 59 | private val IMAGE_REQUEST = arrayOf("来", "图") 60 | 61 | /** 62 | * Runtime fields 63 | */ 64 | internal val config = RosemoePluginConfig 65 | 66 | internal val dispatcher = CommandDispatcher(this, coroutineContext) 67 | private val msgs = MessageStates() 68 | 69 | override fun onEnable() { 70 | super.onEnable() 71 | /*try { 72 | logger.verbose("正在检查插件更新...") 73 | val source = getWebpageSource("https://github.com/Rosemoe/BotPlugin/releases/latest") 74 | } catch (e: Exception) { 75 | logger.warning("检查插件更新失败", e) 76 | }*/ 77 | initOrReloadConfig() 78 | globalEventChannel(this.coroutineContext).registerListenerHost(this) 79 | registerCommands() 80 | startRecallManager() 81 | msgs.launchClearer(this) 82 | } 83 | 84 | private fun registerCommands() { 85 | dispatcher.register(Blocklist, Help, Pixiv, Settings, Setu, Sources) 86 | } 87 | 88 | private fun isManagementCommand(command: Command) : Boolean { 89 | return command == Blocklist || command == Sources || command == Settings 90 | } 91 | 92 | override fun shouldRunCommand(user: User, command: Command, group: Long): Boolean { 93 | if (group != 0L && isDarklistGroup(group)) { 94 | return config.managers.contains(user.id) && isManagementCommand(command) 95 | } else { 96 | return true 97 | } 98 | } 99 | 100 | @EventHandler 101 | @Suppress("unused") 102 | fun onFriendMessage(event: FriendMessageEvent) { 103 | try { 104 | // Dispatch message 105 | if (isModuleEnabled("ImageSender") && (event.message.containsTexts(IMAGE_REQUEST) || event.message.containsImage( 106 | "B407F708-A2C6-A506-3420-98DF7CAC4A57" 107 | )) 108 | ) { 109 | sendImageForEvent(MsgEvent(event)) 110 | } 111 | dispatcher.dispatch(event) 112 | } catch (e: Throwable) { 113 | pluginLaunch { 114 | event.sender.sendMessage(getExceptionInfo(e)) 115 | } 116 | logger.error(e) 117 | } 118 | } 119 | 120 | @EventHandler 121 | @Suppress("unused") 122 | fun onGroupTempMessage(event: GroupTempMessageEvent) { 123 | try { 124 | // Dispatch message 125 | if (isModuleEnabled("ImageSender") && (event.message.containsTexts(IMAGE_REQUEST) || event.message.containsImage( 126 | "B407F708-A2C6-A506-3420-98DF7CAC4A57" 127 | )) 128 | ) { 129 | sendImageForEvent(MsgEvent(event)) 130 | } 131 | dispatcher.dispatch(event) 132 | } catch (e: Throwable) { 133 | pluginLaunch { 134 | event.sender.sendMessage(getExceptionInfo(e)) 135 | } 136 | logger.error(e) 137 | } 138 | } 139 | 140 | @EventHandler 141 | @Suppress("unused") 142 | fun onGroupMessage(event: GroupMessageEvent) { 143 | if (msgs.handle(event)) { 144 | try { 145 | // Dispatch message 146 | if (isModuleEnabled("ImageSender") && (event.message.containsTexts(IMAGE_REQUEST) || event.message.containsImage( 147 | "B407F708-A2C6-A506-3420-98DF7CAC4A57" 148 | )) 149 | ) { 150 | sendImageForEvent(MsgEvent(event)) 151 | } 152 | randomRepeat(event) 153 | handleAtReply(event) 154 | handleGpMessageForRepeating(event) 155 | dispatcher.dispatch(event) 156 | } catch (e: Throwable) { 157 | event.sendAsync(getExceptionInfo(e)) 158 | logger.error(e) 159 | } 160 | } 161 | } 162 | 163 | fun isDarklistGroup(event: GroupEvent): Boolean { 164 | return isDarklistGroup(event.group.id) 165 | } 166 | 167 | private fun isDarklistGroup(id: Long): Boolean { 168 | return config.darkListGroups.contains(id) 169 | } 170 | 171 | @EventHandler 172 | @Suppress("unused") 173 | suspend fun onMemberMute(event: MemberMuteEvent) { 174 | if (isDarklistGroup(event)) { 175 | return 176 | } 177 | if (!isModuleEnabled("MuteTip")) { 178 | return 179 | } 180 | event.group.sendMessage( 181 | messageChainOf( 182 | At(event.member), PlainText( 183 | " 喝下了${ 184 | if (event.operator?.permission == MemberPermission.OWNER) "群主" else "管理" 185 | }的红茶,睡了过去" 186 | ) 187 | ) 188 | ) 189 | } 190 | 191 | @EventHandler 192 | @Suppress("unused") 193 | suspend fun onMemberUnmute(event: MemberUnmuteEvent) { 194 | if (isDarklistGroup(event)) { 195 | return 196 | } 197 | if (!isModuleEnabled("MuteTip")) { 198 | return 199 | } 200 | event.group.sendMessage( 201 | messageChainOf( 202 | At(event.member), PlainText(" 被先辈叫醒了") 203 | ) 204 | ) 205 | } 206 | 207 | private fun processFormat(format: String, event: GroupMemberEvent) : String { 208 | return format.replace("\$nick", event.user.nameCardOrNick).replace("\$id", event.user.id.toString()) 209 | } 210 | 211 | @EventHandler 212 | @Suppress("unused") 213 | suspend fun onMemberJoin(event: MemberJoinEvent) { 214 | if (isDarklistGroup(event)) { 215 | return 216 | } 217 | if (!isModuleEnabled("Welcome")) { 218 | return 219 | } 220 | event.group.sendMessage(processFormat(config.msgOnJoinFormat, event)) 221 | } 222 | 223 | @EventHandler 224 | @Suppress("unused") 225 | suspend fun onMemberLeave(event: MemberLeaveEvent) { 226 | if (isDarklistGroup(event)) { 227 | return 228 | } 229 | if (!isModuleEnabled("Welcome")) { 230 | return 231 | } 232 | event.group.sendMessage(processFormat(config.msgOnLeaveFormat, event)) 233 | } 234 | 235 | @EventHandler 236 | @Suppress("unused") 237 | suspend fun onMemberNudge(event: NudgeEvent) { 238 | if (event.subject is Group) { 239 | val group = event.subject as Group 240 | if (isDarklistGroup(group.id) || !isModuleEnabled("PetPet")) { 241 | return 242 | } 243 | val url = event.target.avatarUrl.replace("s=640", "s=100") 244 | generateGifAndSend(url, group, event.target.id) 245 | } 246 | 247 | } 248 | 249 | internal fun isModuleEnabled(name: String): Boolean { 250 | return config.states.getOrDefault(name, true) 251 | } 252 | 253 | internal fun initOrReloadConfig() { 254 | reloadBaseConfig() 255 | initializeImageList() 256 | } 257 | 258 | internal fun reloadBaseConfig() { 259 | reloadPluginConfig(config) 260 | reloadPluginConfig(ImageSourceConfig) 261 | dispatcher.prefix = if (config.commandPrefix.isBlank()) "/" else config.commandPrefix 262 | applyProxySettings() 263 | } 264 | 265 | suspend fun GroupMessageEvent.sendBack(reply: Message): MessageReceipt { 266 | return group.sendMessage(reply) 267 | } 268 | 269 | suspend fun GroupMessageEvent.sendBack(reply: String): MessageReceipt { 270 | return group.sendMessage(reply) 271 | } 272 | 273 | fun GroupMessageEvent.sendAsync(reply: Message) { 274 | pluginLaunch { 275 | group.sendMessage(reply) 276 | } 277 | } 278 | 279 | fun GroupMessageEvent.sendAsync(reply: String) { 280 | pluginLaunch { 281 | group.sendMessage(reply) 282 | } 283 | } 284 | 285 | fun pluginLaunch(action: suspend () -> Unit) { 286 | RosemoePlugin.launch(coroutineContext) { 287 | action() 288 | } 289 | } 290 | 291 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/rosemoe/miraiPlugin/RosemoePluginConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * RosemoeBotPlugin 3 | * Copyright (C) 2020-2021 Rosemoe 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package io.github.rosemoe.miraiPlugin 20 | 21 | import net.mamoe.mirai.console.data.AutoSavePluginConfig 22 | import net.mamoe.mirai.console.data.value 23 | 24 | /** 25 | * Config for [RosemoePlugin] 26 | */ 27 | object RosemoePluginConfig : AutoSavePluginConfig("PluginConfig") { 28 | 29 | /** 30 | * Top-level users for User Access Control 31 | */ 32 | var managers: MutableList by value() 33 | 34 | /** 35 | * Switches for modules and other functions 36 | */ 37 | var states: MutableMap by value() 38 | 39 | /** 40 | * Prefix in group for commands outside 41 | */ 42 | var commandPrefix by value("/") 43 | 44 | /** 45 | * List of ignored groups ids 46 | */ 47 | var darkListGroups: MutableList by value() 48 | 49 | /** 50 | * Delay for image recalling 51 | */ 52 | var imageRecallDelay by value(60000L) 53 | 54 | /** 55 | * Min interval for recalling a message 56 | */ 57 | var recallMinPeriod by value(180L) 58 | 59 | /** 60 | * Max Image count for batch image sender (In one request) 61 | */ 62 | var maxImageRequestCount by value(16) 63 | 64 | var proxyEnabled by value(false) 65 | 66 | var proxyType by value("http") 67 | 68 | var proxyAddress by value("127.0.0.1") 69 | 70 | var proxyPort by value(1080) 71 | 72 | var allowR18ImageInPixiv by value(false) 73 | 74 | var repeatFactor by value(0.05) 75 | 76 | var msgOnJoinFormat by value("欢迎\$nick加入本群~") 77 | 78 | var msgOnLeaveFormat by value("\$nick (\$id) 离开了我们...") 79 | 80 | var repeatDetectGroups: MutableList by value() 81 | 82 | var messageRecordSize by value(12) 83 | 84 | var messageDuplicateLimit by value(6) 85 | 86 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/rosemoe/miraiPlugin/Version.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * RosemoeBotPlugin 3 | * Copyright (C) 2020-2021 Rosemoe 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package io.github.rosemoe.miraiPlugin 20 | 21 | const val pluginVersion = "2.4.2" 22 | const val pluginBuildTime = "2021/7/3 13:50" -------------------------------------------------------------------------------- /src/main/kotlin/io/github/rosemoe/miraiPlugin/command/Command.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * RosemoeBotPlugin 3 | * Copyright (C) 2020-2021 Rosemoe 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package io.github.rosemoe.miraiPlugin.command 20 | 21 | /** 22 | * Implements actions of a command 23 | */ 24 | abstract class Command(val description: CommandDescription) -------------------------------------------------------------------------------- /src/main/kotlin/io/github/rosemoe/miraiPlugin/command/CommandDispatcher.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * RosemoeBotPlugin 3 | * Copyright (C) 2020-2021 Rosemoe 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package io.github.rosemoe.miraiPlugin.command 20 | 21 | import io.github.rosemoe.miraiPlugin.RosemoePlugin 22 | import kotlinx.coroutines.CoroutineScope 23 | import kotlinx.coroutines.launch 24 | import net.mamoe.mirai.event.events.* 25 | import net.mamoe.mirai.message.data.Message 26 | import net.mamoe.mirai.message.data.MessageChain 27 | import net.mamoe.mirai.message.data.MessageSource 28 | import java.util.HashMap 29 | import kotlin.coroutines.CoroutineContext 30 | import kotlin.reflect.KClass 31 | import kotlin.reflect.KFunction 32 | import kotlin.reflect.KVisibility 33 | import kotlin.reflect.full.callSuspend 34 | import kotlin.reflect.full.memberFunctions 35 | import kotlin.reflect.jvm.jvmErasure 36 | 37 | class CommandDispatcher constructor( 38 | private val permissionChecker: CommandPermissionChecker, 39 | override val coroutineContext: CoroutineContext 40 | ) : CoroutineScope { 41 | 42 | var prefix = "/" 43 | 44 | private val commands: MutableList = mutableListOf() 45 | private val commandTree: MutableMap = mutableMapOf() 46 | private val commandFallback = mutableMapOf>() 47 | 48 | fun register(vararg commands: Command) { 49 | commands.forEach { 50 | register(command = it) 51 | } 52 | } 53 | 54 | fun register(command: Command) { 55 | if (!commands.contains(command)) { 56 | commands.add(command) 57 | } 58 | val node = Node() 59 | //println("Reg: $command") 60 | command::class.memberFunctions.forEach { func -> 61 | func.targetAnnotation()?.let { 62 | if ((func.visibility == null || func.visibility == KVisibility.PUBLIC) && func.parameters.size == 2 && func.parameterClassAt( 63 | 1 64 | ).qualifiedName == "io.github.rosemoe.miraiPlugin.command.MsgEvent" 65 | ) { 66 | //println("Register method") 67 | if (it.path == "") { 68 | commandFallback[command] = func 69 | } else { 70 | register(node, it.path, func) 71 | } 72 | } else { 73 | //println("Func: ${func} ${func.parameters.size} ${if(func.parameters.size > 1) func.parameterClassAt(1) else null}") 74 | } 75 | } 76 | } 77 | commandTree[command] = node 78 | } 79 | 80 | fun dispatch(event: MessageEvent) { 81 | var text = StringBuilder().also { 82 | toString(event.message, it) 83 | }.toString() 84 | if (text.startsWith(prefix)) { 85 | text = text.substring(prefix.length) 86 | var regionStart = getNextWordStart(0, text) 87 | var regionEnd = getWordEnd(regionStart, text) 88 | var state = 0 89 | var node: Node? = null 90 | val optionStates = OptionStates() 91 | var fallback: KFunction<*>? = null 92 | lateinit var pendingOption: String 93 | lateinit var options: Array