getValues(boolean deep);
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Infinite Bot v4
2 |
3 | ```
4 | .___ _____.__ .__ __ __________ __ _____
5 | | | _____/ ____\__| ____ |__|/ |_ ____\______ \ _____/ |_ / | |
6 | | |/ \ __\| |/ \| \ __\/ __ \| | _// _ \ __\ / | |_
7 | | | | \ | | | | \ || | \ ___/| | ( <_> ) | / ^ /
8 | |___|___| /__| |__|___| /__||__| \___ >______ /\____/|__| \____ |
9 | \/ \/ \/ \/ |__|
10 | ```
11 |
12 | ## 简介
13 |
14 | 第四代 Infinite QQ 机器人。基于 perpetua-sdk-for-java,为 JavaSE 与 Bukkit 环境下 QQ 机器人的开发提供附属注册、配置管理、事件分发、等功能支持。
15 |
16 | ## 声明
17 |
18 | - 若您在使用时有任何疑问,欢迎入群讨论咨询 `QQ: 863522624`
19 |
20 | - 若您为 Minecraft 公益服主且服务器资源难以承受 perpetua 的运行,欢迎 [[联系我]](https://api.vvhan.com/api/qqCard?qq=765743073) 。我与我的云服务很乐意为您提供一份力所能及的援助。
21 |
22 | ## 致谢
23 |
24 | - 感谢 小豆子、阿丽塔、polar、一口小雨、黑土、仔仔 等腐竹在测试、策划方面提供的帮助与支持
25 |
26 | - 感谢机器人插件的先驱者 [@Albert](https://github.com/mcdoeswhat)
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Document
6 |
7 |
8 |
9 |
10 |
11 |
12 | Loading ...
13 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/adapter/Bootstrap.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.api.adapter;
2 |
3 | import java.io.File;
4 | import java.io.InputStream;
5 | import java.util.logging.Logger;
6 |
7 | public interface Bootstrap {
8 |
9 | /**
10 | * 保存文件资源到磁盘
11 | * @param replace 是否替换
12 | * */
13 | void saveResource(String fileName, boolean replace);
14 |
15 | /**
16 | * 创建配置类实例
17 | * */
18 | Configuration createConfig();
19 |
20 | /**
21 | * 获取资源文件夹
22 | * */
23 | File getDataFolder();
24 |
25 | /**
26 | * 获取日志实例
27 | * */
28 | Logger getLogger();
29 |
30 | /**
31 | * 获取 jar 内资源文件输入流
32 | * @param fileName 文件路径
33 | * */
34 | InputStream getResource(String fileName);
35 |
36 | /**
37 | * 获取当前启动器类加载器
38 | * */
39 | ClassLoader getInstClassLoader();
40 |
41 | Type getType();
42 |
43 | enum Type {
44 | // bukkit server
45 | BUKKIT,
46 | // bungee server
47 | BUNGEE,
48 | // j2se main
49 | ORIGIN
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/repository/PlayerDataRepository.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.repository;
2 |
3 | import com.illtamer.infinite.bot.minecraft.pojo.PlayerData;
4 | import org.jetbrains.annotations.NotNull;
5 | import org.jetbrains.annotations.Nullable;
6 |
7 | import java.util.UUID;
8 |
9 | public interface PlayerDataRepository {
10 |
11 | boolean save(@NotNull PlayerData data);
12 |
13 | @Nullable
14 | PlayerData queryByUUID(@NotNull UUID uuid);
15 |
16 | @Nullable
17 | PlayerData queryByUUID(@NotNull String uuid);
18 |
19 | @Nullable
20 | PlayerData queryByUserId(@NotNull Long userId);
21 |
22 | /**
23 | * 更新玩家数据
24 | * @return 更新前的玩家数据
25 | * */
26 | @Nullable
27 | PlayerData update(@NotNull PlayerData data);
28 |
29 | /**
30 | * 删除玩家数据
31 | * @return 移除前的玩家数据
32 | * */
33 | @Nullable
34 | PlayerData delete(@NotNull String uuid);
35 |
36 | /**
37 | * 删除玩家数据
38 | * @return 移除前的玩家数据
39 | * */
40 | @Nullable
41 | PlayerData delete(@NotNull Long userId);
42 |
43 | /**
44 | * 将缓存数据写入硬盘
45 | * */
46 | void saveCacheData();
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/IExternalExpansion.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.api;
2 |
3 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap;
4 | import org.bukkit.plugin.Plugin;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | /**
8 | * Infinite Bot 外部扩展附属接口
9 | * */
10 | public interface IExternalExpansion extends IExpansion {
11 |
12 | /**
13 | * 主动注册附属 TODO 测试位于附属目录下能否正常使用,能否检测不正确加载
14 | * @apiNote 主动注册时,附属不可位于附属目录下,否则将被重复注册抛出异常
15 | * */
16 | void register(@NotNull Plugin plugin);
17 |
18 | /**
19 | * 主动卸载附属
20 | * */
21 | void unregister();
22 |
23 | /**
24 | * 附属是否已被注册
25 | * */
26 | boolean isRegister();
27 |
28 | /**
29 | * 是否允许注册
30 | * */
31 | default boolean canRegister() {
32 | return true;
33 | }
34 |
35 | /**
36 | * 是否为持久化实例
37 | *
38 | * 当此方法返回为 true 时,指令将不会卸载实例(其余途径如注册插件自身被卸载、IB3被卸载等仍会触发卸载)
39 | * */
40 | default boolean isPersist() {
41 | return true;
42 | }
43 |
44 | /**
45 | * 获取插件本体实例
46 | * */
47 | default BukkitBootstrap getPluginInstance() {
48 | return BukkitBootstrap.getInstance();
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/start/origin/OriginBootstrap.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.start.origin;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap;
4 | import com.illtamer.infinite.bot.minecraft.api.adapter.Configuration;
5 |
6 | import java.io.File;
7 | import java.io.InputStream;
8 | import java.util.logging.Logger;
9 |
10 | // TODO
11 | public class OriginBootstrap implements Bootstrap {
12 |
13 | @Override
14 | public void saveResource(String fileName, boolean replace) {
15 |
16 | }
17 |
18 | @Override
19 | public Configuration createConfig() {
20 | return null;
21 | }
22 |
23 | @Override
24 | public File getDataFolder() {
25 | return null;
26 | }
27 |
28 | @Override
29 | public Logger getLogger() {
30 | return null;
31 | }
32 |
33 | @Override
34 | public InputStream getResource(String fileName) {
35 | return null;
36 | }
37 |
38 | @Override
39 | public ClassLoader getInstClassLoader() {
40 | return null;
41 | }
42 |
43 | @Override
44 | public Type getType() {
45 | return Type.ORIGIN;
46 | }
47 |
48 | public static void main(String[] args) {
49 | // TODO
50 |
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/ThreadPoolUtil.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.util;
2 |
3 | public final class ThreadPoolUtil {
4 |
5 | private ThreadPoolUtil() {}
6 |
7 | /**
8 | * Each tasks blocks 90% of the time, and works only 10% of its
9 | * lifetime. That is, I/O intensive pool
10 | * @return io intensive Thread pool size
11 | */
12 | public static int ioIntensivePoolSize() {
13 | double blockingCoefficient = 0.8;
14 | return poolSize(blockingCoefficient);
15 | }
16 |
17 | /**
18 | *
19 | * Number of threads = Number of Available Cores / (1 - Blocking
20 | * Coefficient) where the blocking coefficient is between 0 and 1.
21 | *
22 | * A computation-intensive task has a blocking coefficient of 0, whereas an
23 | * IO-intensive task has a value close to 1,
24 | * so we don't have to worry about the value reaching 1.
25 | * @param blockingCoefficient the coefficient
26 | * @return Thread pool size
27 | */
28 | public static int poolSize(double blockingCoefficient) {
29 | int numberOfCores = Runtime.getRuntime().availableProcessors();
30 | return (int) (numberOfCores / (1 - blockingCoefficient));
31 | }
32 | }
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/ProcessBar.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.util;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.StaticAPI;
4 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap;
5 |
6 | public class ProcessBar {
7 |
8 | private final String endStr;
9 |
10 | private int current;
11 | private final int total;
12 |
13 | private ProcessBar(int total) {
14 | this.total = total;
15 | this.current = 0;
16 | this.endStr = StaticAPI.getInstance().getType() == Bootstrap.Type.BUKKIT ? "]\n" : "]";
17 | }
18 |
19 | /**
20 | * 增加进度
21 | * */
22 | public synchronized void count() {
23 | current += 1;
24 | // print, check finish
25 | print();
26 | }
27 |
28 | private void print() {
29 | System.out.printf("\r%d/%d [", current, total);
30 | for (int j = 0; j < current; ++ j) {
31 | System.out.print("=");
32 | }
33 | for (int j = current; j < total; ++ j) {
34 | System.out.print(" ");
35 | }
36 | System.out.print(endStr);
37 | if (current == total) {
38 | System.out.println();
39 | }
40 | }
41 |
42 | public static ProcessBar create(int total) {
43 | return new ProcessBar(total);
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/IExpansion.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.api;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.slf4j.Logger;
5 |
6 | import java.io.File;
7 | import java.io.InputStream;
8 |
9 | /**
10 | * Infinite Bot 附属接口
11 | * */
12 | public interface IExpansion {
13 |
14 | /**
15 | * 附属启用时调用
16 | * */
17 | void onEnable();
18 |
19 | /**
20 | * 附属卸载时调用
21 | * */
22 | void onDisable();
23 |
24 | /**
25 | * 附属是否被加载
26 | * */
27 | boolean isEnabled();
28 |
29 | /**
30 | * 获取附属名称
31 | * @apiNote 名称为任意不包含 '-' 的一串字符
32 | * */
33 | String getExpansionName();
34 |
35 | /**
36 | * 获取附属版本
37 | * @apiNote 语义化版本
38 | * */
39 | String getVersion();
40 |
41 | /**
42 | * 获取附属作者
43 | * @apiNote 任意字符
44 | * */
45 | String getAuthor();
46 |
47 | /**
48 | * 获取当前附属logger
49 | * */
50 | Logger getLogger();
51 |
52 | /**
53 | * 获取当前附属配置文件夹
54 | * */
55 | File getDataFolder();
56 |
57 | /**
58 | * 获取附属资源文件
59 | * */
60 | InputStream getResource(String name);
61 |
62 | /**
63 | * 储存附属jar中的资源文件到附属文件夹
64 | * */
65 | void saveResource(String path, boolean replace);
66 |
67 | @Override
68 | @NotNull
69 | String toString();
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/configuration/config/DataSourceConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.configuration.config;
2 |
3 | import com.zaxxer.hikari.HikariConfig;
4 | import com.zaxxer.hikari.HikariDataSource;
5 |
6 | import javax.sql.DataSource;
7 | import java.util.Map;
8 |
9 | public class DataSourceConfiguration {
10 |
11 | private final HikariDataSource dataSource;
12 |
13 | public DataSourceConfiguration() {
14 | final Map mysqlConfig = BotConfiguration.database.mysqlConfig;
15 |
16 | HikariConfig config = new HikariConfig();
17 | config.setJdbcUrl("jdbc:mysql://" + mysqlConfig.get("host") + ":" + mysqlConfig.get("port") + "/" + mysqlConfig.get("database"));
18 | config.setUsername((String) mysqlConfig.get("username"));
19 | config.setPassword((String) mysqlConfig.get("password"));
20 | config.setMaximumPoolSize(20); // 根据需要调整,建议 10~30
21 | config.setMinimumIdle(5);
22 | config.setConnectionTimeout(30_000);
23 | config.setIdleTimeout(600_000);
24 | config.setMaxLifetime(1800_000);
25 | config.setLeakDetectionThreshold(60_000); // 60秒未归还视为泄漏(开发环境可用)
26 | config.setAutoCommit(true);
27 | this.dataSource = new HikariDataSource(config);
28 | }
29 |
30 | public DataSource getDataSource() {
31 | return dataSource;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/listener/PluginListener.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.listener;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.IExternalExpansion;
4 | import com.illtamer.infinite.bot.minecraft.expansion.ExpansionLoader;
5 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap;
6 | import org.bukkit.event.EventHandler;
7 | import org.bukkit.event.EventPriority;
8 | import org.bukkit.event.Listener;
9 | import org.bukkit.event.server.PluginDisableEvent;
10 | import org.bukkit.plugin.Plugin;
11 |
12 | import java.util.logging.Logger;
13 |
14 | public class PluginListener implements Listener {
15 |
16 | private final ExpansionLoader expansionLoader;
17 | private final Logger logger;
18 |
19 | public PluginListener(BukkitBootstrap instance) {
20 | this.expansionLoader = instance.getExpansionLoader();
21 | this.logger = instance.getLogger();
22 | }
23 |
24 | @EventHandler(priority = EventPriority.MONITOR)
25 | public void onDisabled(PluginDisableEvent event) {
26 | final Plugin plugin = event.getPlugin();
27 | final IExternalExpansion externalExpansion = expansionLoader.getExternalExpansion(plugin);
28 | if (externalExpansion != null) {
29 | logger.warning("插件 " + plugin.getName() + " 在卸载时未主动注销附属,被动注销中...");
30 | expansionLoader.disableExternalExpansion(externalExpansion);
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/Optional.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.util;
2 |
3 | import com.illtamer.perpetua.sdk.util.Assert;
4 |
5 | import java.util.function.Consumer;
6 | import java.util.function.Supplier;
7 |
8 | public final class Optional {
9 |
10 | private T value;
11 |
12 | private Optional() {}
13 |
14 | private Optional(T value) {
15 | this.value = value;
16 | }
17 |
18 | public void ifPresent(Consumer super T> consumer) {
19 | if (value != null)
20 | consumer.accept(value);
21 | }
22 |
23 | public T orElseThrow(Supplier extends X> exceptionSupplier) throws X {
24 | if (value != null) {
25 | return value;
26 | } else {
27 | throw exceptionSupplier.get();
28 | }
29 | }
30 |
31 | public void set(T value) {
32 | Assert.isNull(this.value, "Optional value exists!");
33 | this.value = value;
34 | }
35 |
36 | public T get() {
37 | Assert.notNull(value, "Unexpect null value.");
38 | return value;
39 | }
40 |
41 | public static Optional empty() {
42 | return new Optional<>();
43 | }
44 |
45 | public static Optional of(T value) {
46 | return new Optional<>(value);
47 | }
48 |
49 | public static Optional ofNullable(T value) {
50 | return value == null ? empty() : of(value);
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/AutoConfigUtil.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.util;
2 |
3 | import lombok.experimental.UtilityClass;
4 | import org.jetbrains.annotations.Nullable;
5 |
6 | import java.util.HashMap;
7 | import java.util.Map;
8 | import java.util.function.Function;
9 |
10 | @UtilityClass
11 | public class AutoConfigUtil {
12 |
13 | private static final Map, Function> DEFAULT_CAST_MAP = new HashMap<>();
14 |
15 | static {
16 | DEFAULT_CAST_MAP.put(String.class, s -> s);
17 | DEFAULT_CAST_MAP.put(Integer.class, Integer::parseInt);
18 | DEFAULT_CAST_MAP.put(Long.class, Long::parseLong);
19 | DEFAULT_CAST_MAP.put(Float.class, Float::parseFloat);
20 | DEFAULT_CAST_MAP.put(Double.class, Double::parseDouble);
21 | DEFAULT_CAST_MAP.put(Byte.class, Byte::parseByte);
22 | DEFAULT_CAST_MAP.put(Short.class, Short::parseShort);
23 | }
24 |
25 | @Nullable
26 | public static Object castDefaultBasicType(String defaultValue, Class> fieldType) {
27 | for (Map.Entry, Function> entry : DEFAULT_CAST_MAP.entrySet()) {
28 | if (entry.getKey().equals(fieldType)) {
29 | try {
30 | return entry.getValue().apply(defaultValue);
31 | } catch (Exception e) {
32 | e.printStackTrace();
33 | }
34 | break;
35 | }
36 | }
37 | return null;
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/minecraft/src/main/resources/config.yml:
--------------------------------------------------------------------------------
1 | # .___ _____.__ .__ __ __________ __ ________
2 | # | | _____/ ____\__| ____ |__|/ |_ ____\______ \ _____/ |_ \_____ \
3 | # | |/ \ __\| |/ \| \ __\/ __ \| | _// _ \ __\ _(__ <
4 | # | | | \ | | | | \ || | \ ___/| | ( <_> ) | / \
5 | # |___|___| /__| |__|___| /__||__| \___ >______ /\____/|__| /______ /
6 | # \/ \/ \/ \/ \/
7 | #
8 | # - by IllTamer
9 | # GitHub:
10 | # - 前置: https://github.com/IUnlimit/perpetua
11 | # - 本插件: https://github.com/IllTamer/infinitebot4
12 |
13 | # 插件配置部分
14 | # 该配置可重载
15 | main:
16 | # 管理员账号列表
17 | admins:
18 | - 765743073
19 | # 监听的群列表
20 | # bungee: true 时此配置项无效
21 | groups:
22 | - 863522624
23 |
24 | # 数据储存方式
25 | # 该配置仅在加载插件时读取
26 | database:
27 | # 可选择的储存方式
28 | # 'yaml' - 本地yaml储存,数据仅插件加载服可用
29 | # 'mysql' - mysql数据库储存,群组服数据互通必选
30 | type: 'yaml'
31 | config:
32 | mysql:
33 | host: 'localhost'
34 | port: 3306
35 | username: 'root'
36 | password: 'root'
37 | database: 'minecraft'
38 |
39 | # 连接配置项
40 | # 该配置可重载
41 | connection:
42 | # 当前客户端连接名称
43 | # 可为空,为空时当前连接不配置名称
44 | # 当附属使用分布式 API 时,建议配置,配置时应确保该名称全局唯一
45 | name: 'ib-1'
46 | # perpetua webapi 配置
47 | webapi:
48 | # 进程所在域名
49 | host: '127.0.0.1'
50 | # 服务开放端口
51 | port: 8080
52 | # 通信验证 token
53 | # 若您在 OneBot 实现中配置了此项,请填写,否则请留空
54 | authorization: ''
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/configuration/StatusCheckRunner.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.configuration;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.StaticAPI;
4 | import com.illtamer.perpetua.sdk.entity.transfer.entity.LoginInfo;
5 | import com.illtamer.perpetua.sdk.entity.transfer.entity.Status;
6 | import com.illtamer.perpetua.sdk.handler.OpenAPIHandling;
7 | import com.illtamer.perpetua.sdk.websocket.OneBotConnection;
8 | import lombok.Getter;
9 | import org.jetbrains.annotations.Nullable;
10 |
11 | import java.util.logging.Logger;
12 |
13 | /**
14 | * 机器人状态检查 Runnable
15 | * */
16 | public class StatusCheckRunner implements Runnable {
17 |
18 | @Getter
19 | private static long lastRefreshTime;
20 | @Getter
21 | @Nullable
22 | private static LoginInfo loginInfo;
23 |
24 | private final Logger log;
25 |
26 | public StatusCheckRunner(Logger logger) {
27 | this.log = logger;
28 | }
29 |
30 | @Override
31 | public void run() {
32 | if (!OneBotConnection.isRunning()) {
33 | log.warning("检测到 WebSocket 连接断开,尝试重连 perpetua 中");
34 | loginInfo = null;
35 | } else {
36 | try {
37 | loginInfo = OpenAPIHandling.getLoginInfo();
38 | } catch (Exception ignore) {
39 | log.warning("获取账号信息失败,尝试重连 perpetua 中");
40 | loginInfo = null;
41 | }
42 | }
43 | if (loginInfo == null) {
44 | StaticAPI.reconnected();
45 | }
46 | lastRefreshTime = System.currentTimeMillis();
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/listener/BungeeCommandListener.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.listener;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap;
4 | import com.illtamer.infinite.bot.minecraft.listener.handler.CommandHelper;
5 | import net.md_5.bungee.api.CommandSender;
6 | import net.md_5.bungee.api.chat.TextComponent;
7 | import net.md_5.bungee.api.plugin.Command;
8 |
9 | import java.util.Arrays;
10 |
11 | public class BungeeCommandListener extends Command {
12 |
13 | public BungeeCommandListener() {
14 | super("InfiniteBot3", null, "ib3");
15 | }
16 |
17 | @Override
18 | public void execute(CommandSender sender, String[] args) {
19 | CommandHelper.onCommand(new com.illtamer.infinite.bot.minecraft.api.adapter.CommandSender() {
20 | @Override
21 | public boolean isOp() {
22 | return sender.hasPermission("ib3-op-test-permission");
23 | }
24 |
25 | @Override
26 | public void sendMessage(String message) {
27 | TextComponent comp = new TextComponent();
28 | comp.setText(message);
29 | sender.sendMessage(comp);
30 | }
31 |
32 | @Override
33 | public void sendMessage(String[] messages) {
34 | TextComponent[] components = Arrays.stream(messages).map(message -> {
35 | TextComponent comp = new TextComponent();
36 | comp.setText(message);
37 | return comp;
38 | }).toArray(TextComponent[]::new);
39 | sender.sendMessage(components);
40 | }
41 | }, args, Bootstrap.Type.BUNGEE);
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/docs/zh-cn/use-minecraft.md:
--------------------------------------------------------------------------------
1 | # for Minecraft
2 |
3 | 基于 go-cqhttp 提供的 API 服务,IB3 天然支持群组服/分布式服务。
4 |
5 | 您需要做的仅仅是在 BC 端上加载插件,与需要互通的其它子端配置相同地连接参数与 mysql 服务。
6 | 接着,您可以在不同的子端上加载不同的附属:如登陆服加载 defence-manager 开启白名单验证码服务,在生存服加载 chat-manager 运行消息互通服务...
7 |
8 | (好叭,其实 BC 端正在适配中)
9 |
10 | ## 环境
11 |
12 | ### Java 8
13 |
14 | 该版本可直接运行运行插件
15 |
16 | ### Java 9+
17 |
18 | 若您的服务器使用了 Java9 及以上的版本,则需在启动的批处理文件中加入以下 JVM 参数允许来自未命名模块的反射调用。
19 |
20 | ```cmd
21 | --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED
22 | ```
23 |
24 | ## 部署
25 |
26 | - 插件
27 |
28 | > **Notice**: 插件本体理论支持 热加载&热卸载,但不建议 **频繁的热部署插件**。插件附属管理为插件内部支持实现,其完全支持热部署,可放心食用。
29 |
30 | 请您将构建的 `InfiniteBot-minecraft-all-3.0.0-SNAPSHOT.jar` 放入服务器的 `plugins` 文件夹内。
31 | 待生成配置文件后,请参考 [[config.yml]](src/main/resources/config.yml) 内注释详细配置 go-cqhttp 的连接信息。
32 |
33 | - 插件附属
34 |
35 | 遵守[附属开发规范](docs/Expansion.md)的插件附属应当被放置在 `plugins\InfiniteBot3\expansions` 路径内。若附属注册了相应的配置文件,则在附属被加载后与附属注册名称相同的配置文件夹也将在同级目录下生成。
36 |
37 | ## 指令
38 |
39 | 所有的指令与补全仅管理员(`OP`)可用
40 |
41 | ```text
42 | ib3:
43 | ├──help: 获取帮助
44 | ├──check: 检查账号的连接状态
45 | ├──reload: 重载配置文件
46 | ├──expansions
47 | │ ├──list: 列出所有加载的附属名称
48 | │ └──reload: 重载 expansions 目录下所有附属
49 | ├──load
50 | │ └──[附属文件名]: 加载名称对应附属
51 | └──unload
52 | └──[附属名称]: 卸载名称对应附属
53 | ```
54 |
55 | ## 资源文件夹结构
56 |
57 | ```text
58 | InfiniteBot3
59 | ├──expansions: 附属及其配置文件夹
60 | │ ├──basic-manager-1.0.jar: 基础管理插件
61 | │ ├──BasicManager: 基础管理插件配置文件夹
62 | │ │ └──config.yml: 基础管理插件配置文件
63 | │ └──...
64 | ├──config.yml: 插件本体配置文件
65 | └──player_data.yml:
66 | ```
67 |
68 | ## 附属相关
69 |
70 | ### 下载
71 |
72 | 详见仓库 [infinite-bot-3-expansion](https://github.com/IllTamer/infinite-bot-3-expansion)
73 |
74 |
75 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/configuration/config/ConfigFile.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.configuration.config;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap;
4 | import com.illtamer.infinite.bot.minecraft.api.adapter.Configuration;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 |
9 | public class ConfigFile {
10 | private final String name;
11 | private final Bootstrap instance;
12 | private File file;
13 | private volatile Configuration config;
14 |
15 | public ConfigFile(String name, Bootstrap instance) {
16 | this.name = name;
17 | this.instance = instance;
18 | this.config = this.load();
19 | }
20 |
21 | public void save() {
22 | try {
23 | this.config.save(this.file);
24 | } catch (IOException var2) {
25 | var2.printStackTrace();
26 | }
27 | this.config = this.load();
28 | }
29 |
30 | /**
31 | * 更新并保存配置文件
32 | * */
33 | public void update(String yaml) {
34 | config.load(yaml);
35 | save();
36 | }
37 |
38 | private Configuration load() {
39 | File file = new File(this.instance.getDataFolder(), this.name);
40 | if (!file.exists()) {
41 | this.instance.saveResource(this.name, false);
42 | }
43 |
44 | Configuration configuration = instance.createConfig();
45 | try {
46 | configuration.load(file);
47 | this.file = file;
48 | } catch (IOException var4) {
49 | var4.printStackTrace();
50 | }
51 | return configuration;
52 | }
53 |
54 | public void reload() {
55 | this.config = this.load();
56 | }
57 |
58 | public Configuration getConfig() {
59 | return this.config;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/listener/BukkitCommandListener.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.listener;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap;
4 | import com.illtamer.infinite.bot.minecraft.listener.handler.CommandHelper;
5 | import org.bukkit.command.Command;
6 | import org.bukkit.command.CommandSender;
7 | import org.bukkit.command.TabExecutor;
8 | import org.jetbrains.annotations.NotNull;
9 | import org.jetbrains.annotations.Nullable;
10 |
11 | import java.util.List;
12 |
13 | public class BukkitCommandListener implements TabExecutor {
14 |
15 | @Override
16 | public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
17 | return CommandHelper.onCommand(new BukkitCommandSender(sender), args, Bootstrap.Type.BUKKIT);
18 | }
19 |
20 | @Nullable
21 | @Override
22 | public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
23 | return CommandHelper.onTabComplete(new BukkitCommandSender(sender), args);
24 | }
25 |
26 | private static class BukkitCommandSender implements com.illtamer.infinite.bot.minecraft.api.adapter.CommandSender {
27 |
28 | private final CommandSender sender;
29 |
30 | private BukkitCommandSender(CommandSender sender) {
31 | this.sender = sender;
32 | }
33 |
34 | @Override
35 | public boolean isOp() {
36 | return sender.isOp();
37 | }
38 |
39 | @Override
40 | public void sendMessage(String message) {
41 | sender.sendMessage(message);
42 | }
43 |
44 | @Override
45 | public void sendMessage(String[] messages) {
46 | sender.sendMessage(messages);
47 | }
48 |
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/pojo/PlayerData.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.pojo;
2 |
3 | import lombok.Data;
4 | import org.bukkit.Bukkit;
5 | import org.bukkit.OfflinePlayer;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | import java.util.LinkedHashMap;
9 | import java.util.List;
10 | import java.util.Map;
11 | import java.util.UUID;
12 |
13 | @Data
14 | public class PlayerData {
15 |
16 | /**
17 | * Use for database
18 | * */
19 | private Integer id;
20 |
21 | /**
22 | * Minecraft UUID
23 | * */
24 | private String uuid;
25 |
26 | /**
27 | * Valid UUID by MoJang
28 | * */
29 | private String validUUID;
30 |
31 | /**
32 | * qq 号
33 | * */
34 | private Long userId;
35 |
36 | private List permission;
37 |
38 | /**
39 | * 获取倾向的首个不为空的 uuid
40 | *
41 | * TODO 倾向可配置
42 | * */
43 | @Nullable
44 | public String getPreferUUID() {
45 | return uuid == null ? validUUID : uuid;
46 | }
47 |
48 | @Nullable
49 | public OfflinePlayer getValidOfflinePlayer() {
50 | return validUUID != null ? Bukkit.getOfflinePlayer(UUID.fromString(validUUID)) : null;
51 | }
52 |
53 | @Nullable
54 | public OfflinePlayer getOfflinePlayer() {
55 | return uuid != null ? Bukkit.getOfflinePlayer(UUID.fromString(uuid)) : null;
56 | }
57 |
58 | /**
59 | * Use for yaml
60 | * */
61 | public Map toMap() {
62 | LinkedHashMap map = new LinkedHashMap<>(3);
63 | if (uuid != null)
64 | map.put("uuid", uuid);
65 | if (validUUID != null)
66 | map.put("user_name", validUUID);
67 | if (userId != null)
68 | map.put("user_id", userId);
69 | if (permission != null && permission.size() != 0)
70 | map.put("permission", permission);
71 | return map;
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/PluginUtil.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.util;
2 |
3 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap;
4 | import lombok.experimental.UtilityClass;
5 |
6 | @UtilityClass
7 | public class PluginUtil {
8 |
9 | public static String parseColor(String s) {
10 | return s.replace('&', '§').replace("§§", "&");
11 | }
12 |
13 | public static String clearColor(String s) {
14 | char[] chars = s.toCharArray();
15 | StringBuilder builder = new StringBuilder();
16 |
17 | for(int i = 0; i < chars.length; ++i) {
18 | if (chars[i] == 167 && i + 1 <= chars.length) {
19 | char next = chars[i + 1];
20 | if (checkColor(next)) {
21 | ++i;
22 | continue;
23 | }
24 | }
25 | builder.append(chars[i]);
26 | }
27 | return builder.toString();
28 | }
29 |
30 | private static boolean checkColor(char next) {
31 | if (next >= '0' && next <= '9') return true;
32 | if ((next >= 'a' && next <= 'f') || (next >= 'A' && next <= 'F')) return true;
33 | if ((next >= 'k' && next <= 'o') || (next >= 'K' && next <= 'O')) return true;
34 | return next == 'r' || next == 'x' || next == 'R' || next == 'X';
35 | }
36 |
37 | public static class Version {
38 |
39 | public static final String VERSION;
40 |
41 | static {
42 | final String packageName = BukkitBootstrap.getInstance().getServer().getClass().getPackage().getName();
43 | VERSION = packageName.substring(packageName.lastIndexOf('.') + 1);
44 | }
45 |
46 | /**
47 | * @param number 中版本号,例如 v1_12_R1 -> 12
48 | * */
49 | public static boolean upper(int number) {
50 | final String[] split = VERSION.split("_");
51 | return Integer.parseInt(split[1]) > number;
52 | }
53 |
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/BotScheduler.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.api;
2 |
3 | import com.google.common.util.concurrent.ThreadFactoryBuilder;
4 | import com.illtamer.infinite.bot.minecraft.util.ThreadPoolUtil;
5 |
6 | import java.util.concurrent.*;
7 |
8 | public class BotScheduler {
9 |
10 | public static final ExecutorService IO_INTENSIVE;
11 | public static final ScheduledExecutorService TIMER;
12 |
13 | public static void runTask(Runnable runnable) {
14 | IO_INTENSIVE.submit(runnable);
15 | }
16 |
17 | public static void runTask(Callable callable) {
18 | IO_INTENSIVE.submit(callable);
19 | }
20 |
21 | public static Future runTask(Runnable runnable, T t) {
22 | return IO_INTENSIVE.submit(runnable, t);
23 | }
24 |
25 | public static ScheduledFuture> runTaskLater(Runnable runnable, long delaySeconds) {
26 | return TIMER.schedule(runnable, delaySeconds, TimeUnit.SECONDS);
27 | }
28 |
29 | public static ScheduledFuture> runTaskTimer(Runnable runnable, long initDelay, long periodSeconds) {
30 | return TIMER.scheduleAtFixedRate(runnable, initDelay, periodSeconds, TimeUnit.SECONDS);
31 | }
32 |
33 | public static void close() {
34 | IO_INTENSIVE.shutdownNow();
35 | TIMER.shutdownNow();
36 | }
37 |
38 | static {
39 | TIMER = new ScheduledThreadPoolExecutor(1,
40 | new ThreadFactoryBuilder()
41 | .setNameFormat("bot-schedule-%d")
42 | .setUncaughtExceptionHandler((t, e) -> e.printStackTrace())
43 | .build());
44 | IO_INTENSIVE = new ThreadPoolExecutor(
45 | 3,
46 | ThreadPoolUtil.poolSize(0.90),
47 | 60L, TimeUnit.SECONDS,
48 | new SynchronousQueue<>(),
49 | new ThreadFactoryBuilder()
50 | .setNameFormat("bot-thread-%d")
51 | .setUncaughtExceptionHandler((t, e) -> e.printStackTrace())
52 | .build());
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/Language.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.expansion;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.IExpansion;
4 | import com.illtamer.perpetua.sdk.util.Assert;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | import java.util.Optional;
8 |
9 | /**
10 | * 语言消息配置类
11 | * */
12 | public class Language {
13 |
14 | private final ExpansionConfig lanConfig;
15 |
16 | protected Language(String fileName, int version, IExpansion expansion) {
17 | this.lanConfig = new ExpansionConfig(fileName + ".yml", expansion, version);
18 | }
19 |
20 | /**
21 | * 获取语言文本
22 | * @param nodes 配置节点
23 | * */
24 | @NotNull
25 | public String get(String... nodes) {
26 | Assert.notEmpty(nodes, "Language nodes can not be empty!");
27 | StringBuilder builder = new StringBuilder();
28 | for (String node : nodes) {
29 | Assert.notEmpty(node, "Language node can not be null!");
30 | builder.append('.').append(node);
31 | }
32 | builder.deleteCharAt(0);
33 | return Optional.ofNullable(lanConfig.getConfig().getString(builder.toString())).orElse("");
34 | }
35 |
36 | /**
37 | * 重新载入语言文件
38 | * */
39 | public void reload() {
40 | lanConfig.reload();
41 | }
42 |
43 | public static Language of(IExpansion expansion) {
44 | return of("language", expansion);
45 | }
46 |
47 | /**
48 | * @param name 语言文件前缀名 language
49 | * */
50 | public static Language of(String name, IExpansion expansion) {
51 | return of(name, 0, "zh_CN", expansion);
52 | }
53 |
54 | /**
55 | * @param name 语言文件前缀名 language
56 | * */
57 | public static Language of(String name, int version, IExpansion expansion) {
58 | return of(name, version, "zh_CN", expansion);
59 | }
60 |
61 |
62 | /**
63 | * @param name 语言文件前缀名 language
64 | * @param type 语言类型后缀 zh_CN
65 | * */
66 | public static Language of(String name, int version, String type, IExpansion expansion) {
67 | return new Language(name + '-' + type, version, expansion);
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Infinite Bot v4
2 |
3 | ```
4 | .___ _____.__ .__ __ __________ __ _____
5 | | | _____/ ____\__| ____ |__|/ |_ ____\______ \ _____/ |_ / | |
6 | | |/ \ __\| |/ \| \ __\/ __ \| | _// _ \ __\ / | |_
7 | | | | \ | | | | \ || | \ ___/| | ( <_> ) | / ^ /
8 | |___|___| /__| |__|___| /__||__| \___ >______ /\____/|__| \____ |
9 | \/ \/ \/ \/ |__|
10 | ```
11 |
12 |
13 | 第四代 Infinite QQ 机器人。基于 perpetua-sdk-for-java,为 JavaSE 与 Bukkit 环境下 QQ 机器人的开发提供附属注册、配置管理、事件分发、等功能支持。
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | [Expansions]
24 | [Document (维护中)]
25 |
26 |
27 | ## Import
28 |
29 | ### Maven
30 |
31 | ```xml
32 |
33 | iunlimit-releases
34 | IllTamer's Repository
35 | https://maven.illtamer.com/releases
36 |
37 | ```
38 |
39 | ```xml
40 |
41 | com.illtamer.infinite.bot
42 | minecraft
43 | {version}
44 |
45 | ```
46 |
47 | ### Gradle
48 |
49 | ```groovy
50 | maven {
51 | url "https://maven.illtamer.com/releases"
52 | }
53 | ```
54 |
55 | ```groovy
56 | implementation 'com.illtamer.infinite.bot:minecraft:{version}'
57 | ```
58 |
59 | ## 声明
60 |
61 | - 若您在使用时有任何疑问,欢迎入群讨论咨询 `QQ: 863522624`
62 |
63 | - 若您为 Minecraft 公益服主且服务器资源难以承受 perpetua 的运行,欢迎 [[联系我]](https://api.vvhan.com/api/qqCard?qq=765743073) 。我与我的云服务很乐意为您提供一份力所能及的援助。
64 |
65 | ## 致谢
66 |
67 | - 感谢 小豆子、阿丽塔、polar、一口小雨、黑土、仔仔 等腐竹在测试、策划方面提供的帮助与支持
68 |
69 | - 感谢机器人插件的先驱者 [@Albert](https://github.com/mcdoeswhat)
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/automation/factory/SingletonFactory.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.expansion.automation.factory;
2 |
3 | import com.illtamer.infinite.bot.minecraft.exception.InitializationException;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import javax.management.openmbean.KeyAlreadyExistsException;
7 | import java.lang.reflect.Constructor;
8 | import java.lang.reflect.InvocationTargetException;
9 | import java.util.HashMap;
10 |
11 | /**
12 | * 单例工厂
13 | * */
14 | public class SingletonFactory {
15 |
16 | private static final HashMap, Object> SINGLETONS = new HashMap<>(1 << 6);
17 |
18 | private SingletonFactory() {}
19 |
20 | /**
21 | * 获取单例对象
22 | * @apiNote 传入类必须有无参构造或已被注册
23 | * */
24 | @NotNull
25 | public static T getInstance(Class clazz) {
26 | if (clazz == null)
27 | throw new NullPointerException();
28 |
29 | Object instance = SINGLETONS.get(clazz);
30 | if (instance != null)
31 | return clazz.cast(instance);
32 |
33 | instance = createInstance(clazz);
34 | return clazz.cast(instance);
35 | }
36 |
37 | /**
38 | * 设置单例对象
39 | * */
40 | public static void setInstance(Class> clazz, Object object) {
41 | if (!clazz.isInstance(object))
42 | throw new InitializationException("Mismatched class and instance");
43 | if (SINGLETONS.containsKey(clazz))
44 | throw new KeyAlreadyExistsException();
45 | SINGLETONS.put(clazz, object);
46 | }
47 |
48 | /**
49 | * 移除单例
50 | * */
51 | public static T remove(Class clazz) {
52 | return clazz.cast(SINGLETONS.remove(clazz));
53 | }
54 |
55 | @NotNull
56 | private synchronized static Object createInstance(Class> clazz) {
57 | Object instance = SINGLETONS.get(clazz);
58 | if (instance != null) return instance;
59 | try {
60 | Constructor> constructor = clazz.getDeclaredConstructor();
61 | constructor.setAccessible(true);
62 | instance = constructor.newInstance();
63 | SINGLETONS.put(clazz, instance);
64 | return instance;
65 | } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
66 | throw new InitializationException(e);
67 | }
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/ValidUtil.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.util;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.JsonObject;
5 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap;
6 | import com.illtamer.perpetua.sdk.Pair;
7 | import com.illtamer.perpetua.sdk.util.HttpRequestUtil;
8 | import lombok.experimental.UtilityClass;
9 | import org.bukkit.entity.Player;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | import java.util.UUID;
13 |
14 | @UtilityClass
15 | public class ValidUtil {
16 | private static final String SESSION_SERVER = "https://sessionserver.mojang.com/session/minecraft/profile/";
17 | private static final Gson GSON = new Gson();
18 |
19 | public static boolean isValidPlayer(@NotNull Player player) {
20 | return isValid(player.getUniqueId(), player.getName());
21 | }
22 |
23 | public static boolean isValid(@NotNull UUID uuid, @NotNull String name) {
24 | return isValid(uuid.toString(), name);
25 | }
26 |
27 | public static boolean isValid(@NotNull String uuid, @NotNull String name) {
28 | try {
29 | final Pair pair = HttpRequestUtil.getJson(SESSION_SERVER + uuid, null);
30 | final Integer status = pair.getKey();
31 | if (status == 400) return false;
32 | if (status != 200) {
33 | BukkitBootstrap.getInstance().getLogger().warning("正版UUID验证服务器不可用,状态码: " + status);
34 | return false;
35 | }
36 | final JsonObject object = GSON.fromJson(pair.getValue(), JsonObject.class);
37 | return name.equals(object.get("name").getAsString());
38 | } catch (Exception e) {
39 | e.printStackTrace();
40 | }
41 | return false;
42 | }
43 |
44 | public static boolean isValidUUID(@NotNull UUID uuid) {
45 | return isValidUUID(uuid.toString());
46 | }
47 |
48 | public static boolean isValidUUID(@NotNull String uuid) {
49 | try {
50 | final Pair pair = HttpRequestUtil.getJson(SESSION_SERVER + uuid, null);
51 | final Integer status = pair.getKey();
52 | if (status == 200) return true;
53 | BukkitBootstrap.getInstance().getLogger().warning("正版UUID验证服务器不可用,状态码: " + status);
54 | } catch (Exception e) {
55 | System.err.println(e.getMessage());
56 | }
57 | return false;
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/start/origin/OriginConfig.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.start.origin;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.adapter.Configuration;
4 | import org.jetbrains.annotations.Nullable;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 | import java.util.List;
9 | import java.util.Map;
10 | import java.util.Set;
11 |
12 | // TODO
13 | class OriginConfig implements Configuration {
14 |
15 | @Override
16 | public String getString(String path) {
17 | return null;
18 | }
19 |
20 | @Override
21 | public String getString(String path, String def) {
22 | return null;
23 | }
24 |
25 | @Override
26 | public List getStringList(String path) {
27 | return null;
28 | }
29 |
30 | @Override
31 | public Integer getInt(String path) {
32 | return null;
33 | }
34 |
35 | @Override
36 | public Integer getInt(String path, Integer def) {
37 | return null;
38 | }
39 |
40 | @Override
41 | public Long getLong(String path) {
42 | return null;
43 | }
44 |
45 | @Override
46 | public Long getLong(String path, Long def) {
47 | return null;
48 | }
49 |
50 | @Override
51 | public Boolean getBoolean(String path) {
52 | return null;
53 | }
54 |
55 | @Override
56 | public Boolean getBoolean(String path, Boolean def) {
57 | return null;
58 | }
59 |
60 | @Override
61 | public List getLongList(String path) {
62 | return null;
63 | }
64 |
65 | @Override
66 | public void set(String path, Object value) {
67 |
68 | }
69 |
70 | @Nullable
71 | @Override
72 | public Configuration getSection(String path) {
73 | return null;
74 | }
75 |
76 | @Override
77 | public Configuration createSection(String path, Map data) {
78 | return null;
79 | }
80 |
81 | @Override
82 | public String saveToString() {
83 | return null;
84 | }
85 |
86 | @Override
87 | public Set getKeys(boolean deep) {
88 | return null;
89 | }
90 |
91 | @Override
92 | public Map getValues(boolean deep) {
93 | return null;
94 | }
95 |
96 | @Override
97 | public void save(File file) throws IOException {
98 |
99 | }
100 |
101 | @Override
102 | public void load(File file) throws IOException {
103 |
104 | }
105 |
106 | @Override
107 | public void load(String yaml) {
108 |
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/automation/Registration.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.expansion.automation;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.IExpansion;
4 | import com.illtamer.infinite.bot.minecraft.exception.InitializationException;
5 | import com.illtamer.infinite.bot.minecraft.expansion.ExpansionConfig;
6 | import com.illtamer.infinite.bot.minecraft.expansion.automation.factory.SingletonFactory;
7 | import lombok.SneakyThrows;
8 | import org.bukkit.configuration.file.FileConfiguration;
9 | import org.bukkit.configuration.serialization.ConfigurationSerialization;
10 |
11 | import java.util.HashMap;
12 | import java.util.HashSet;
13 | import java.util.Map;
14 | import java.util.Set;
15 |
16 | /**
17 | * Enum 字段在注册前应重写 #toString() 和 #valueOf(), 以便于正确进行序列化和反序列化
18 | * */
19 | public class Registration {
20 |
21 | private static final Map>> autoConfigMap = new HashMap<>();
22 |
23 | /**
24 | * 注册自动配置类
25 | * @apiNote 请在 onEnable 方法中调用
26 | * */
27 | @SneakyThrows
28 | public static void add(AutoLoadConfiguration instance, IExpansion expansion) {
29 | final Class extends AutoLoadConfiguration> clazz = instance.getClass();
30 | final Set> set = autoConfigMap.computeIfAbsent(expansion, k -> new HashSet<>());
31 | if (set.contains(clazz))
32 | throw new IllegalArgumentException("Duplicate auto-config class: " + clazz);
33 | set.add(clazz);
34 | ConfigurationSerialization.registerClass(clazz);
35 | SingletonFactory.setInstance(clazz, instance);
36 | }
37 |
38 | /**
39 | * 获取自动配置类单例
40 | * */
41 | public static T get(Class clazz) {
42 | try {
43 | return SingletonFactory.getInstance(clazz);
44 | } catch (InitializationException e) {
45 | throw new IllegalArgumentException("未注册的自动配置类: " + clazz);
46 | }
47 | }
48 |
49 | /**
50 | * 注销并保存所有自动配置文件
51 | * */
52 | public static void removeAndStoreAutoConfigs(IExpansion expansion) {
53 | final Set> set = autoConfigMap.remove(expansion);
54 | if (set == null || set.size() == 0) return;
55 | for (Class extends AutoLoadConfiguration> clazz : set) {
56 | final AutoLoadConfiguration configuration = SingletonFactory.remove(clazz);
57 | final ExpansionConfig configFile = configuration.getConfigFile();
58 | final FileConfiguration config = configFile.getConfig();
59 | for (Map.Entry entry : configuration.serialize().entrySet()) {
60 | config.set(entry.getKey(), entry.getValue());
61 | }
62 | configFile.save();
63 | ConfigurationSerialization.unregisterClass(clazz);
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/StringUtil.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.util;
2 |
3 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap;
4 | import lombok.experimental.UtilityClass;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | import java.util.ArrayList;
8 | import java.util.Arrays;
9 | import java.util.List;
10 | import java.util.logging.Logger;
11 |
12 | @UtilityClass
13 | public class StringUtil {
14 |
15 | private static final Logger log = BukkitBootstrap.getInstance().getLogger();
16 |
17 | /**
18 | * @return aaa\nbbb\nccc
19 | * */
20 | public static String toString(List list) {
21 | StringBuilder builder = new StringBuilder();
22 | for (String s : list)
23 | builder.append('\n').append(s);
24 | builder.deleteCharAt(0);
25 | return builder.toString();
26 | }
27 |
28 | public static String[] toArray(List list) {
29 | String[] array = new String[list.size()];
30 | for(int i = 0; i < list.size(); ++i)
31 | array[i] = list.get(i);
32 | return array;
33 | }
34 |
35 | /**
36 | * @return [obj1, obj2]
37 | * */
38 | public static String parseString(List list) {
39 | if (list == null) return null;
40 | if (list.size() == 0) return "[]";
41 | StringBuilder builder = new StringBuilder();
42 | builder.append('[');
43 | for (int i = 0; i < list.size(); i++) {
44 | if (i != 0) builder.append(", ");
45 | builder.append(list.get(i));
46 | }
47 | builder.append(']');
48 | return builder.toString();
49 | }
50 |
51 | /**
52 | * @param list [obj1, obj2]
53 | * */
54 | @NotNull
55 | public static List parseList(String list) {
56 | List result = new ArrayList<>();
57 | if (list == null || list.length() <= 2) return result;
58 | try {
59 | final String[] split = list.substring(1, list.length() - 1).split(", ");
60 | result.addAll(Arrays.asList(split));
61 | } catch (Exception e) {
62 | log.warning("Some errors occurred in the process of parse '" + list + "'");
63 | e.printStackTrace();
64 | }
65 | return result;
66 | }
67 |
68 | /**
69 | * 判断字符串是否为 null、空或仅由空白字符组成。
70 | *
71 | * @param str 要判断的字符串
72 | * @return 如果字符串为 null、空或仅包含空白字符,则返回 true;否则返回 false。
73 | */
74 | public static boolean isBlank(String str) {
75 | if (str == null || str.isEmpty()) {
76 | return true;
77 | }
78 | // 检查是否全部是空白字符
79 | for (int i = 0; i < str.length(); i++) {
80 | if (!Character.isWhitespace(str.charAt(i))) {
81 | return false;
82 | }
83 | }
84 | return true;
85 | }
86 |
87 | /**
88 | * 判断字符串是否不为 null、不为空且至少包含一个非空白字符。
89 | *
90 | * @param str 要判断的字符串
91 | * @return 如果字符串不为 null、不为空且包含非空白字符,则返回 true;否则返回 false。
92 | */
93 | public static boolean isNotBlank(String str) {
94 | return !isBlank(str);
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if %ERRORLEVEL% equ 0 goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if %ERRORLEVEL% equ 0 goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | set EXIT_CODE=%ERRORLEVEL%
84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
86 | exit /b %EXIT_CODE%
87 |
88 | :mainEnd
89 | if "%OS%"=="Windows_NT" endlocal
90 |
91 | :omega
92 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/start/bukkit/BukkitBootstrap.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.start.bukkit;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.BotScheduler;
4 | import com.illtamer.infinite.bot.minecraft.api.EventExecutor;
5 | import com.illtamer.infinite.bot.minecraft.api.StaticAPI;
6 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap;
7 | import com.illtamer.infinite.bot.minecraft.api.adapter.Configuration;
8 | import com.illtamer.infinite.bot.minecraft.configuration.BotNettyHolder;
9 | import com.illtamer.infinite.bot.minecraft.configuration.StatusCheckRunner;
10 | import com.illtamer.infinite.bot.minecraft.configuration.config.BotConfiguration;
11 | import com.illtamer.infinite.bot.minecraft.expansion.ExpansionLoader;
12 | import com.illtamer.infinite.bot.minecraft.listener.BukkitCommandListener;
13 | import com.illtamer.infinite.bot.minecraft.listener.PluginListener;
14 | import com.illtamer.infinite.bot.minecraft.util.Optional;
15 | import lombok.Getter;
16 | import org.bukkit.command.PluginCommand;
17 | import org.bukkit.plugin.java.JavaPlugin;
18 |
19 | // TODO 集中help管理、command支持
20 | // libs folder
21 | public class BukkitBootstrap extends JavaPlugin implements Bootstrap {
22 |
23 | @Getter
24 | private static BukkitBootstrap instance;
25 |
26 | @Getter
27 | private final ExpansionLoader expansionLoader = new ExpansionLoader(this);
28 | @Getter
29 | private final Optional nettyHolder = Optional.empty();
30 |
31 | @Override
32 | public void onLoad() {
33 | StaticAPI.setInstance(instance = this);
34 | // DependencyLoader.load(instance);
35 | BotConfiguration.load(instance);
36 | this.nettyHolder.set(new BotNettyHolder(getLogger(), EventExecutor::dispatchListener));
37 | nettyHolder.get().connect();
38 | }
39 |
40 | @Override
41 | public void onEnable() {
42 | nettyHolder.ifPresent(BotNettyHolder::checkConnection);
43 | BotScheduler.runTaskTimer(new StatusCheckRunner(getLogger()), 2L, 30);
44 | BotScheduler.runTaskLater(() -> expansionLoader.loadExpansions(false), 3L);
45 | BukkitCommandListener bukkitCommandListener = new BukkitCommandListener();
46 | final PluginCommand command = Optional.ofNullable(getServer().getPluginCommand("InfiniteBot4"))
47 | .orElseThrow(NullPointerException::new);
48 | command.setTabCompleter(bukkitCommandListener);
49 | command.setExecutor(bukkitCommandListener);
50 | getServer().getPluginManager().registerEvents(new PluginListener(this), this);
51 | }
52 |
53 | @Override
54 | public void onDisable() {
55 | BotScheduler.close();
56 | expansionLoader.disableExpansions(false);
57 | BotConfiguration.saveAndClose();
58 | nettyHolder.ifPresent(BotNettyHolder::close);
59 | instance = null;
60 | }
61 |
62 | @Override
63 | public Configuration createConfig() {
64 | return new BukkitConfigSection.Config();
65 | }
66 |
67 | @Override
68 | public ClassLoader getInstClassLoader() {
69 | return getClassLoader();
70 | }
71 |
72 | @Override
73 | public Type getType() {
74 | return Type.BUKKIT;
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/minecraft/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | // id 'dev.vankka.dependencydownload.plugin' version '1.3.1'
3 | id 'com.github.johnrengelman.shadow' version '7.0.0'
4 | }
5 |
6 | dependencies {
7 | implementation 'dev.vankka:dependencydownload-runtime:1.3.1'
8 |
9 | api 'com.illtamer.perpetua.sdk:perpetua-sdk:0.2.2-SNAPSHOT'
10 | api 'org.slf4j:slf4j-api:1.7.32'
11 | implementation 'mysql:mysql-connector-java:8.0.29'
12 | implementation 'com.zaxxer:HikariCP:4.0.3'
13 | implementation 'org.slf4j:slf4j-api:1.7.32'
14 | implementation 'org.slf4j:slf4j-simple:1.7.32'
15 |
16 | compileOnly 'org.spigotmc:spigot-api:1.16.4-R0.1-SNAPSHOT'
17 | compileOnly 'net.md-5:bungeecord-api:1.21-R0.1'
18 | compileOnly 'org.projectlombok:lombok:1.18.26'
19 | annotationProcessor 'org.projectlombok:lombok:1.18.26'
20 | }
21 |
22 | tasks.withType(Javadoc).configureEach {
23 | enabled = false
24 | }
25 |
26 | publishing {
27 | repositories {
28 | if (version.toString().endsWith("-SNAPSHOT")) {
29 | maven {
30 | name "iunlimitSnapshots"
31 | url "https://maven.illtamer.com/snapshots"
32 | credentials(PasswordCredentials)
33 | authentication {
34 | basic(BasicAuthentication)
35 | }
36 | }
37 | } else {
38 | maven {
39 | name = "iunlimitReleases"
40 | url = "https://maven.illtamer.com/releases"
41 | credentials(PasswordCredentials)
42 | authentication {
43 | basic(BasicAuthentication)
44 | }
45 | }
46 | }
47 | }
48 | publications {
49 | mavenJava(MavenPublication) {
50 | groupId = group
51 | artifactId = project.name
52 | version = version
53 | from components.java
54 | }
55 | }
56 | }
57 |
58 | processResources {
59 | filteringCharset = 'UTF-8'
60 | filesMatching('plugin.yml') {
61 | expand('version': project.version)
62 | }
63 | }
64 |
65 | // generate runtimeDownloadOnly.txt
66 | //shadowJar.dependsOn generateRuntimeDownloadResourceForRuntimeDownloadOnly, generateRuntimeDownloadResourceForRuntimeDownload
67 |
68 | shadowJar {
69 | archiveBaseName = parent.name + '-' + project.name
70 | version = project.version
71 | archiveClassifier = 'all'
72 | manifest {
73 | attributes('Automatic-Module-Name': 'com.illtamer.infinite.bot.minecraft')
74 | }
75 | }
76 |
77 | [
78 | 'io.netty',
79 | // 'org.apache.httpcomponents',
80 | // 'commons-logging',
81 | // 'commons-codec',
82 | 'com.google.gson',
83 | // 'com.google.guava',
84 | // com.google.guava
85 | // 'com.google.common',
86 | // 'com.google.code.findbugs',
87 | // 'com.google.errorprone',
88 | // 'com.google.j2objc',
89 | // 'org.checkerframework',
90 | // 'com.google.protobuf',
91 | // 'org.jetbrains',
92 | // 'mysql',
93 | // 'org.slf4j',
94 | ].each {
95 | var relocated = 'com.illtamer.infinite.bot.minecraft.libs.' + it
96 | tasks.shadowJar.relocate it, relocated
97 | // tasks.generateRuntimeDownloadResourceForRuntimeDownloadOnly.relocate it, relocated
98 | }
99 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/manager/AbstractExternalExpansion.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.expansion.manager;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.IExternalExpansion;
4 | import com.illtamer.infinite.bot.minecraft.configuration.config.BotConfiguration;
5 | import com.illtamer.infinite.bot.minecraft.expansion.ExpansionLogger;
6 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap;
7 | import com.illtamer.infinite.bot.minecraft.util.ExpansionUtil;
8 | import com.illtamer.perpetua.sdk.util.Assert;
9 | import org.bukkit.plugin.Plugin;
10 | import org.jetbrains.annotations.NotNull;
11 | import org.slf4j.Logger;
12 |
13 | import java.io.File;
14 | import java.io.InputStream;
15 | import java.util.Objects;
16 |
17 | public abstract class AbstractExternalExpansion implements IExternalExpansion {
18 |
19 | private final ClassLoader classLoader;
20 | private final File dataFolder;
21 | private final ExpansionLogger logger;
22 |
23 | private boolean register;
24 | private boolean enabled;
25 |
26 | public AbstractExternalExpansion() {
27 | this.classLoader = this.getClass().getClassLoader();
28 | Assert.notEmpty(getExpansionName(), "Expansion name can not be empty!");
29 | this.dataFolder = new File(BukkitBootstrap.getInstance().getDataFolder(), '/' + BotConfiguration.EXPANSION_FOLDER_NAME + '/' + getExpansionName());
30 | this.logger = new ExpansionLogger(this);
31 | }
32 |
33 | @Override
34 | public void register(@NotNull Plugin plugin) {
35 | BukkitBootstrap.getInstance().getExpansionLoader().loadExternalExpansion(this, plugin);
36 | register = true;
37 | }
38 |
39 | @Override
40 | public void unregister() {
41 | BukkitBootstrap.getInstance().getExpansionLoader().disableExternalExpansion(this);
42 | register = false;
43 | }
44 |
45 | /**
46 | * bot内部开启/关闭拓展
47 | * */
48 | protected void setEnabled(boolean enabled) {
49 | if (this.enabled != enabled) {
50 | this.enabled = enabled;
51 | if (enabled) {
52 | onEnable();
53 | } else {
54 | onDisable();
55 | }
56 | }
57 | }
58 |
59 | @Override
60 | public Logger getLogger() {
61 | return logger;
62 | }
63 |
64 | @Override
65 | public File getDataFolder() {
66 | return dataFolder;
67 | }
68 |
69 | @Override
70 | public InputStream getResource(String name) {
71 | return ExpansionUtil.getPluginResource(name, classLoader);
72 | }
73 |
74 | @Override
75 | public void saveResource(String path, boolean replace) {
76 | ExpansionUtil.savePluginResource(path, replace, dataFolder, this::getResource);
77 | }
78 |
79 | @Override
80 | public boolean isEnabled() {
81 | return enabled;
82 | }
83 |
84 | @Override
85 | public boolean isRegister() {
86 | return register;
87 | }
88 |
89 | @Override
90 | public int hashCode() {
91 | return Objects.hash(getExpansionName(), getVersion(), getAuthor());
92 | }
93 |
94 | @Override
95 | @NotNull
96 | public String toString() {
97 | return ExpansionUtil.formatIdentifier(this);
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/configuration/DependencyLoader.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.configuration;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.BotScheduler;
4 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap;
5 | import com.illtamer.infinite.bot.minecraft.util.ProcessBar;
6 | import dev.vankka.dependencydownload.DependencyManager;
7 | import dev.vankka.dependencydownload.repository.Repository;
8 | import dev.vankka.dependencydownload.repository.StandardRepository;
9 | import lombok.SneakyThrows;
10 |
11 | import java.io.BufferedReader;
12 | import java.io.File;
13 | import java.io.InputStreamReader;
14 | import java.lang.reflect.Method;
15 | import java.net.URL;
16 | import java.net.URLClassLoader;
17 | import java.util.Arrays;
18 | import java.util.List;
19 | import java.util.concurrent.CompletableFuture;
20 | import java.util.concurrent.ExecutorService;
21 | import java.util.stream.Collectors;
22 |
23 | /**
24 | * 依赖库动态加载器
25 | * */
26 | public class DependencyLoader {
27 |
28 | public static final List REPOSITORIES = Arrays.asList(
29 | new StandardRepository("https://repo.maven.apache.org/maven2"),
30 | new StandardRepository("https://oss.sonatype.org/content/repositories/snapshots"),
31 | new StandardRepository("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
32 | );
33 |
34 | @SneakyThrows
35 | public static void load(Bootstrap instance) {
36 | File dependencyFolder = new File(instance.getDataFolder(), "libs");
37 | if (!dependencyFolder.exists()) dependencyFolder.mkdirs();
38 | ExecutorService executor = BotScheduler.IO_INTENSIVE;
39 |
40 | DependencyManager manager = new DependencyManager(dependencyFolder.toPath());
41 | String resource = new BufferedReader(new InputStreamReader(instance.getResource("runtimeDownload.txt")))
42 | .lines().collect(Collectors.joining(System.lineSeparator()));
43 | manager.loadFromResource(resource);
44 | System.out.println("getRelocations: " + manager.getRelocations().size());
45 |
46 | instance.getLogger().info("Download dependencies ...");
47 | CompletableFuture[] downloadFutures = manager.download(executor, REPOSITORIES);
48 | ProcessBar downloadBar = ProcessBar.create(downloadFutures.length);
49 | for (CompletableFuture future : downloadFutures) {
50 | executor.submit(() -> {
51 | future.join();
52 | downloadBar.count();
53 | });
54 | }
55 |
56 | manager.relocateAll(executor).join();
57 | instance.getLogger().info("Loading dependencies ...");
58 | CompletableFuture[] loadFutures = manager.load(executor, (path) -> {
59 | try {
60 | URLClassLoader classLoader = (URLClassLoader) instance.getInstClassLoader();
61 | Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
62 | addURL.setAccessible(true);
63 | addURL.invoke(classLoader, path.toUri().toURL());
64 | } catch (Exception e) {
65 | e.printStackTrace();
66 | }
67 | });
68 | // ProcessBar loadBar = ProcessBar.create(loadFutures.length);
69 | for (CompletableFuture future : loadFutures) {
70 | executor.submit(() -> {
71 | future.join();
72 | // loadBar.count();
73 | });
74 | }
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/pojo/TimedBlockingCache.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.pojo;
2 |
3 | import lombok.Getter;
4 |
5 | import java.util.*;
6 | import java.util.concurrent.TimeUnit;
7 | import java.util.concurrent.TimeoutException;
8 | import java.util.concurrent.locks.Condition;
9 | import java.util.concurrent.locks.ReentrantLock;
10 |
11 | /**
12 | * TimedBlockingCache
13 | * @apiNote 基于LinkedHashMap实现FIFO容量控制,并使用 ReentrantLock 和 Condition 实现阻塞等待和超时机制
14 | * */
15 | public class TimedBlockingCache {
16 |
17 | @Getter
18 | private final int capacity;
19 | private final LinkedHashMap map;
20 | private final ReentrantLock lock = new ReentrantLock();
21 | private final Map waitConditions = new HashMap<>();
22 |
23 | public TimedBlockingCache(int capacity) {
24 | if (capacity <= 0) {
25 | throw new IllegalArgumentException("Capacity must be positive");
26 | }
27 | this.capacity = capacity;
28 | this.map = new LinkedHashMap(capacity, 0.75f, true) {
29 | @Override
30 | protected boolean removeEldestEntry(Map.Entry eldest) {
31 | return size() > capacity;
32 | }
33 | };
34 | }
35 |
36 | public void put(K key, V value) {
37 | if (value == null) {
38 | throw new NullPointerException("Value cannot be null");
39 | }
40 | lock.lock();
41 | try {
42 | map.put(key, value);
43 | // 如果有线程在等待这个key,唤醒它们
44 | if (waitConditions.containsKey(key)) {
45 | waitConditions.get(key).signalAll();
46 | }
47 | } finally {
48 | lock.unlock();
49 | }
50 | }
51 |
52 | public V get(K key, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
53 | long timeoutMillis = unit.toMillis(timeout);
54 | lock.lock();
55 | try {
56 | if (map.containsKey(key)) {
57 | return map.get(key);
58 | }
59 |
60 | // 创建或获取等待条件
61 | Condition condition = waitConditions.get(key);
62 | if (condition == null) {
63 | condition = lock.newCondition();
64 | waitConditions.put(key, condition);
65 | }
66 |
67 | // 等待超时
68 | boolean timedOut = !condition.await(timeoutMillis, TimeUnit.MILLISECONDS);
69 | if (timedOut) {
70 | // 超时后移除等待条件
71 | waitConditions.remove(key);
72 | throw new TimeoutException("Timeout waiting for key: " + key);
73 | }
74 |
75 | // 被唤醒后检查数据
76 | if (map.containsKey(key)) {
77 | return map.get(key);
78 | } else {
79 | // 理论上不会发生(唤醒时数据应存在)
80 | throw new TimeoutException("Unexpected timeout for key: " + key);
81 | }
82 | } finally {
83 | lock.unlock();
84 | }
85 | }
86 |
87 | public boolean remove(K key) {
88 | lock.lock();
89 | try {
90 | boolean exists = map.containsKey(key);
91 | if (exists) {
92 | map.remove(key);
93 | // 移除等待条件
94 | waitConditions.remove(key);
95 | }
96 | return exists;
97 | } finally {
98 | lock.unlock();
99 | }
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/ExpansionUtil.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.util;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.IExpansion;
4 | import com.illtamer.infinite.bot.minecraft.pojo.ExpansionIdentifier;
5 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap;
6 | import com.illtamer.perpetua.sdk.util.Assert;
7 | import lombok.experimental.UtilityClass;
8 | import org.jetbrains.annotations.NotNull;
9 | import org.jetbrains.annotations.Nullable;
10 |
11 | import java.io.*;
12 | import java.net.URL;
13 | import java.net.URLConnection;
14 | import java.util.function.Function;
15 | import java.util.regex.Matcher;
16 | import java.util.regex.Pattern;
17 |
18 | @UtilityClass
19 | public class ExpansionUtil {
20 |
21 | public static final Pattern IDENTIFIER = Pattern.compile("([^-]*)-(.*)::(.*)");
22 | public static final String FORMAT = "%s-%s::%s";
23 |
24 | @NotNull
25 | public static String formatIdentifier(IExpansion expansion) {
26 | return formatIdentifier(expansion.getExpansionName(), expansion.getVersion(), expansion.getAuthor());
27 | }
28 |
29 | @NotNull
30 | public static String formatIdentifier(String name, String version, String author) {
31 | return String.format(FORMAT, name, version, author);
32 | }
33 |
34 | @Nullable
35 | public static ExpansionIdentifier parseIdentifier(String identifier) {
36 | final Matcher matcher = IDENTIFIER.matcher(identifier);
37 | if (matcher.find()) {
38 | return new ExpansionIdentifier(matcher.group(1), matcher.group(2), matcher.group(3));
39 | }
40 | return null;
41 | }
42 |
43 | @Nullable
44 | public static InputStream getPluginResource(String name, ClassLoader classLoader) {
45 | URL url = classLoader.getResource(name);
46 | if (url == null) {
47 | return null;
48 | }
49 | try {
50 | URLConnection connection = url.openConnection();
51 | connection.setUseCaches(false);
52 | return connection.getInputStream();
53 | } catch (IOException e) {
54 | return null;
55 | }
56 | }
57 |
58 | public static void savePluginResource(String path, boolean replace, File dataFolder, Function inputFunc) {
59 | Assert.isTrue(path != null && !path.isEmpty(), "The resource name can not be null !");
60 | path = path.replace("\\", "/");
61 | InputStream input = inputFunc.apply(path);
62 | Assert.notNull(input, String.format("Can't find the resource '%s'", path));
63 |
64 | File outFile = new File(dataFolder, path);
65 | int lastIndex = path.lastIndexOf('/');
66 | File outDir = new File(dataFolder, path.substring(0, Math.max(lastIndex, 0)));
67 | if (!outDir.exists()) {
68 | outDir.mkdirs();
69 | }
70 | try {
71 | if (!outFile.exists() || replace) {
72 | OutputStream out = new FileOutputStream(outFile);
73 | byte[] buf = new byte[1024];
74 | int len;
75 | while ((len = input.read(buf)) > 0) {
76 | out.write(buf, 0, len);
77 | }
78 | out.close();
79 | input.close();
80 | } else {
81 | BukkitBootstrap.getInstance().getLogger().warning("Could not save " + outFile.getName() + " to " + outFile + " because " + outFile.getName() + " already exists !");
82 | }
83 | } catch (IOException ex) {
84 | BukkitBootstrap.getInstance().getLogger().severe("Could not save " + outFile.getName() + " to " + outFile);
85 | ex.printStackTrace();
86 | }
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/manager/InfiniteExpansion.java:
--------------------------------------------------------------------------------
1 | package com.illtamer.infinite.bot.minecraft.expansion.manager;
2 |
3 | import com.illtamer.infinite.bot.minecraft.api.IExpansion;
4 | import com.illtamer.infinite.bot.minecraft.configuration.config.BotConfiguration;
5 | import com.illtamer.infinite.bot.minecraft.expansion.ExpansionLogger;
6 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap;
7 | import com.illtamer.infinite.bot.minecraft.util.ExpansionUtil;
8 | import org.jetbrains.annotations.NotNull;
9 |
10 | import java.io.File;
11 | import java.io.InputStream;
12 | import java.util.Objects;
13 |
14 | public abstract class InfiniteExpansion implements IExpansion {
15 | private boolean enabled = false;
16 | private InfinitePluginLoader loader;
17 | private File jarFile;
18 | private ClassLoader classLoader;
19 | private ExpansionLogger logger;
20 | private File dataFolder;
21 |
22 | public InfiniteExpansion() {
23 | ClassLoader classLoader = getClass().getClassLoader();
24 | if (!(classLoader instanceof PluginClassLoader)) {
25 | throw new IllegalStateException("InfiniteExpansion requires " + PluginClassLoader.class.getName());
26 | }
27 | ((PluginClassLoader)classLoader).initialize(this);
28 | }
29 |
30 | protected InfiniteExpansion(InfinitePluginLoader loader, File jarFile, String folderName) {
31 | this.loader = loader;
32 | ClassLoader classLoader = getClass().getClassLoader();
33 | if (classLoader instanceof PluginClassLoader) {
34 | throw new IllegalStateException("Cannot use initialization constructor at runtime");
35 | }
36 | init(loader, jarFile, classLoader, folderName);
37 | }
38 |
39 | @Override
40 | public boolean isEnabled() {
41 | return enabled;
42 | }
43 |
44 | final void init(InfinitePluginLoader loader, File jarFile, ClassLoader classLoader, String folderName) {
45 | this.loader = loader;
46 | this.jarFile = jarFile;
47 | this.classLoader = classLoader;
48 | this.logger = new ExpansionLogger(this);
49 | folderName = getExpansionName() != null && getExpansionName().length() != 0 ? getExpansionName() : folderName;
50 | this.dataFolder = new File(BukkitBootstrap.getInstance().getDataFolder(), '/' + BotConfiguration.EXPANSION_FOLDER_NAME + '/' + folderName);
51 | }
52 |
53 | /**
54 | * bot内部开启/关闭拓展
55 | * */
56 | protected void setEnabled(boolean enabled) {
57 | if (this.enabled != enabled) {
58 | this.enabled = enabled;
59 | if (enabled) {
60 | onEnable();
61 | } else {
62 | onDisable();
63 | }
64 | }
65 | }
66 |
67 | protected ClassLoader getClassLoader() {
68 | return classLoader;
69 | }
70 |
71 | @Override
72 | public InputStream getResource(String name) {
73 | return ExpansionUtil.getPluginResource(name, classLoader);
74 | }
75 |
76 | @Override
77 | public void saveResource(String path, boolean replace) {
78 | ExpansionUtil.savePluginResource(path, replace, dataFolder, this::getResource);
79 | }
80 |
81 | @Override
82 | public File getDataFolder() {
83 | return dataFolder;
84 | }
85 |
86 | @Override
87 | public ExpansionLogger getLogger() {
88 | return logger;
89 | }
90 |
91 | @Override
92 | public int hashCode() {
93 | return Objects.hash(getExpansionName(), getVersion(), getAuthor());
94 | }
95 |
96 | @Override
97 | @NotNull
98 | public String toString() {
99 | return ExpansionUtil.formatIdentifier(this);
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/docs/zh-cn/dev-api.md:
--------------------------------------------------------------------------------
1 | # API
2 |
3 | ## 导入
4 |
5 | ### Maven
6 |
7 | InfiniteBot-v3 为支持 go-cqhttp 实现了一系列包括事件监听、消息回调、end-point 快速操作等 API
8 | 。您可将 [[api]](/api) 模块作为调用 go-cqhttp 的前置依赖库导入项目,进行开发。
9 |
10 | ```xml
11 |
12 | com.illtamer.infinite.bot
13 | api
14 | 1.0.5
15 |
16 | ```
17 |
18 | 毫无疑问,IB3 需要您提供您开启的 go-cqhttp 服务的详细参数以便于与机器人建立连接。请在程序中调用以下函数以便于初始化连接。
19 |
20 | ```java
21 | CQHttpWebSocketConfiguration.start(httpUri, wsUri, authorization, eventConsumer);
22 | ```
23 |
24 | 该方法最后一个参数为事件基类 [Event](/api/src/main/java/com/illtamer/infinite/bot/api/event/Event.java) 的 `Consumer exteds Event>` 对象,即事件的处理函数。
25 |
26 | > 在 Spring 框架中,您可以调用 `ApplicationEventPublisher#publish(Object)` 将事件托管。
27 |
28 |
29 | ### SpringBoot
30 |
31 | InfiniteBot-v3 额外优化了在 SpringBoot 框架下的开发体验。您只需要进行相应配置即可使用
32 |
33 | 1. 导入 ib3-spring-boot-starter
34 |
35 | ```xml
36 |
37 | com.illtamer.infinite.bot
38 | ib3-spring-boot-starter
39 | 1.0.5
40 |
41 | ```
42 |
43 | 2. 在`application.yml`填写以下配置节点
44 |
45 | ```yaml
46 | bot:
47 | http-uri: ''
48 | ws-uri: ''
49 | authorization: ''
50 | ```
51 |
52 | ## 示例
53 |
54 | !> InfiniteBot3 暂未计划支持任何频道 API
55 |
56 | ### Message
57 |
58 | go-cqhttp 同时支持使用两类消息 —— CQ码与Json,故 InfiniteBot3 也分别支持两种消息的构建。两类消息分别对应实体类 `CQMessage` 与 `JsonMessage`,他们都有一个共同的抽象父类 `Message` 作为类型声明。
59 |
60 | > 在与 go-cqhttp 的 WebSocket 长连接中,事件中的消息以CQ码的形式被传递与解析。一般的,我们使用Json这种层次分明的数据结构来构建需要发送的消息对象。
61 |
62 | #### 生成
63 |
64 | 您可以使用 `MessageBuilder` 建造者工具类来生成一个 `Message` 实例:
65 |
66 | ```java
67 | Message message = MessageBuilder.json()
68 | .text("Hello World")
69 | .build();
70 | ```
71 |
72 | #### Message Chain
73 |
74 | 在 `Message` 被构造的过程中,其内部还会维护一个 `MessageChain` 对象来描述消息中各组成的类型 `TransferEntity`。[点击查看]((https://github.com/IllTamer/infinitebot3/blob/main/api/src/main/java/com/illtamer/infinite/bot/api/entity/transfer/))支持的类型
75 |
76 | ### Event Channel
77 |
78 | ?> _TODO_ 事件管道相关 API 正在开发中 ~
79 |
80 | ### Web API
81 |
82 | api 模块已内置部分较为常用的 go-cqhttp Web API,您可通过 [[OpenAPIHandling]](https://github.com/IllTamer/infinitebot3/blob/main/api/src/main/java/com/illtamer/infinite/bot/api/handler/OpenAPIHandling.java) 便捷调用所有已支持的 API,或查看其内部实现。
83 |
84 | #### 自定义 APIHandler
85 |
86 | 1. 继承 [[AbstractAPIHandler]](https://github.com/IllTamer/infinitebot3/blob/main/api/src/main/java/com/illtamer/infinite/bot/api/handler/AbstractAPIHandler.java)
87 |
88 | 2. 赋予泛型正确的类型(go-cqhttp Http 接口的返回数据类型)
89 |
90 | 3. 向父类构造器中传入正确的 `endpoint` (相关信息请见 [请求说明](https://docs.go-cqhttp.org/api/#%E8%AF%B7%E6%B1%82%E8%AF%B4%E6%98%8E))
91 |
92 | 4. 将 API 所需参数作为成员变量声明并赋值
93 |
94 | 最终您实现的 APIHandler 应类似以下格式,使用 `APIHandler#request` 方法调用相关 Web API
95 |
96 | ```java
97 | /**
98 | * 获取陌生人信息
99 | * */
100 | @Getter
101 | public class StrangerGetHandler extends AbstractAPIHandler