callable) {
633 | super(chartId);
634 | this.callable = callable;
635 | }
636 |
637 | @Override
638 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
639 | int value = callable.call();
640 | if (value == 0) {
641 | // Null = skip the chart
642 | return null;
643 | }
644 | return new JsonObjectBuilder().appendField("value", value).build();
645 | }
646 | }
647 |
648 | /**
649 | * An extremely simple JSON builder.
650 | *
651 | * While this class is neither feature-rich nor the most performant one, it's sufficient enough
652 | * for its use-case.
653 | */
654 | public static class JsonObjectBuilder {
655 |
656 | private StringBuilder builder = new StringBuilder();
657 |
658 | private boolean hasAtLeastOneField = false;
659 |
660 | public JsonObjectBuilder() {
661 | builder.append("{");
662 | }
663 |
664 | /**
665 | * Appends a null field to the JSON.
666 | *
667 | * @param key The key of the field.
668 | * @return A reference to this object.
669 | */
670 | public JsonObjectBuilder appendNull(String key) {
671 | appendFieldUnescaped(key, "null");
672 | return this;
673 | }
674 |
675 | /**
676 | * Appends a string field to the JSON.
677 | *
678 | * @param key The key of the field.
679 | * @param value The value of the field.
680 | * @return A reference to this object.
681 | */
682 | public JsonObjectBuilder appendField(String key, String value) {
683 | if (value == null) {
684 | throw new IllegalArgumentException("JSON value must not be null");
685 | }
686 | appendFieldUnescaped(key, "\"" + escape(value) + "\"");
687 | return this;
688 | }
689 |
690 | /**
691 | * Appends an integer field to the JSON.
692 | *
693 | * @param key The key of the field.
694 | * @param value The value of the field.
695 | * @return A reference to this object.
696 | */
697 | public JsonObjectBuilder appendField(String key, int value) {
698 | appendFieldUnescaped(key, String.valueOf(value));
699 | return this;
700 | }
701 |
702 | /**
703 | * Appends an object to the JSON.
704 | *
705 | * @param key The key of the field.
706 | * @param object The object.
707 | * @return A reference to this object.
708 | */
709 | public JsonObjectBuilder appendField(String key, JsonObject object) {
710 | if (object == null) {
711 | throw new IllegalArgumentException("JSON object must not be null");
712 | }
713 | appendFieldUnescaped(key, object.toString());
714 | return this;
715 | }
716 |
717 | /**
718 | * Appends a string array to the JSON.
719 | *
720 | * @param key The key of the field.
721 | * @param values The string array.
722 | * @return A reference to this object.
723 | */
724 | public JsonObjectBuilder appendField(String key, String[] values) {
725 | if (values == null) {
726 | throw new IllegalArgumentException("JSON values must not be null");
727 | }
728 | String escapedValues =
729 | Arrays.stream(values)
730 | .map(value -> "\"" + escape(value) + "\"")
731 | .collect(Collectors.joining(","));
732 | appendFieldUnescaped(key, "[" + escapedValues + "]");
733 | return this;
734 | }
735 |
736 | /**
737 | * Appends an integer array to the JSON.
738 | *
739 | * @param key The key of the field.
740 | * @param values The integer array.
741 | * @return A reference to this object.
742 | */
743 | public JsonObjectBuilder appendField(String key, int[] values) {
744 | if (values == null) {
745 | throw new IllegalArgumentException("JSON values must not be null");
746 | }
747 | String escapedValues =
748 | Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(","));
749 | appendFieldUnescaped(key, "[" + escapedValues + "]");
750 | return this;
751 | }
752 |
753 | /**
754 | * Appends an object array to the JSON.
755 | *
756 | * @param key The key of the field.
757 | * @param values The integer array.
758 | * @return A reference to this object.
759 | */
760 | public JsonObjectBuilder appendField(String key, JsonObject[] values) {
761 | if (values == null) {
762 | throw new IllegalArgumentException("JSON values must not be null");
763 | }
764 | String escapedValues =
765 | Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(","));
766 | appendFieldUnescaped(key, "[" + escapedValues + "]");
767 | return this;
768 | }
769 |
770 | /**
771 | * Appends a field to the object.
772 | *
773 | * @param key The key of the field.
774 | * @param escapedValue The escaped value of the field.
775 | */
776 | private void appendFieldUnescaped(String key, String escapedValue) {
777 | if (builder == null) {
778 | throw new IllegalStateException("JSON has already been built");
779 | }
780 | if (key == null) {
781 | throw new IllegalArgumentException("JSON key must not be null");
782 | }
783 | if (hasAtLeastOneField) {
784 | builder.append(",");
785 | }
786 | builder.append("\"").append(escape(key)).append("\":").append(escapedValue);
787 | hasAtLeastOneField = true;
788 | }
789 |
790 | /**
791 | * Builds the JSON string and invalidates this builder.
792 | *
793 | * @return The built JSON string.
794 | */
795 | public JsonObject build() {
796 | if (builder == null) {
797 | throw new IllegalStateException("JSON has already been built");
798 | }
799 | JsonObject object = new JsonObject(builder.append("}").toString());
800 | builder = null;
801 | return object;
802 | }
803 |
804 | /**
805 | * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt.
806 | *
807 | *
This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'.
808 | * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n").
809 | *
810 | * @param value The value to escape.
811 | * @return The escaped value.
812 | */
813 | private static String escape(String value) {
814 | final StringBuilder builder = new StringBuilder();
815 | for (int i = 0; i < value.length(); i++) {
816 | char c = value.charAt(i);
817 | if (c == '"') {
818 | builder.append("\\\"");
819 | } else if (c == '\\') {
820 | builder.append("\\\\");
821 | } else if (c <= '\u000F') {
822 | builder.append("\\u000").append(Integer.toHexString(c));
823 | } else if (c <= '\u001F') {
824 | builder.append("\\u00").append(Integer.toHexString(c));
825 | } else {
826 | builder.append(c);
827 | }
828 | }
829 | return builder.toString();
830 | }
831 |
832 | /**
833 | * A super simple representation of a JSON object.
834 | *
835 | *
This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not
836 | * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String,
837 | * JsonObject)}.
838 | */
839 | public static class JsonObject {
840 |
841 | private final String value;
842 |
843 | private JsonObject(String value) {
844 | this.value = value;
845 | }
846 |
847 | @Override
848 | public String toString() {
849 | return value;
850 | }
851 | }
852 | }
853 | }
--------------------------------------------------------------------------------
/src/main/java/io/github/aplini/chat2qq/utils/Util.java:
--------------------------------------------------------------------------------
1 | package io.github.aplini.chat2qq.utils;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.JsonArray;
5 | import com.google.gson.JsonObject;
6 | import io.github.aplini.chat2qq.Chat2QQ;
7 | import me.clip.placeholderapi.PlaceholderAPI;
8 | import me.dreamvoid.miraimc.api.MiraiBot;
9 | import org.bukkit.plugin.Plugin;
10 |
11 | import java.io.IOException;
12 | import java.nio.file.Files;
13 | import java.nio.file.Paths;
14 | import java.util.*;
15 | import java.util.concurrent.ExecutorService;
16 | import java.util.concurrent.Executors;
17 | import java.util.regex.Matcher;
18 | import java.util.regex.Pattern;
19 |
20 | import static org.bukkit.Bukkit.getLogger;
21 |
22 | public class Util {
23 | // 发送消息到群
24 | public static void sendToGroup(long botID, long groupID, String message) {
25 | try {
26 | MiraiBot.getBot(botID).getGroup(groupID).sendMessageMirai(message);
27 | } catch (NoSuchElementException e) {
28 | getLogger().warning("[Chat2QQ] 发送消息出现异常: botID="+ botID +" -> groupID="+ groupID +": "+ e);
29 | }
30 | }
31 | // 简化调用
32 | public static void sendToGroup(Plugin plugin, long groupID, String message) {
33 | sendToGroup(plugin.getConfig().getLongList("bot.bot-accounts").get(0), groupID, message);
34 | }
35 |
36 |
37 | // 从群缓存中获取一个群中的玩家的名称
38 | public static String getNameFromCache(Plugin plugin, long groupID, long playerID, String defaultName) {
39 | if(plugin.getConfig().getBoolean("aplini.player-cache.enabled", false)){
40 | Map group_cache = Chat2QQ.group_cache_all.get(groupID);
41 | if(group_cache != null){
42 | String name = group_cache.get(playerID);
43 | if(name != null){
44 | return name;
45 | }
46 | }
47 | return String.valueOf(playerID);
48 | }
49 | return defaultName;
50 | }
51 |
52 |
53 | // 从群名片匹配出MC标准名称
54 | public static String cleanupName(Plugin plugin, String name, Long qqID) {
55 | if(plugin.getConfig().getBoolean("aplini.cleanup-name.enabled",false)){
56 | Matcher matcher = Pattern.compile(plugin.getConfig().getString("aplini.cleanup-name.regex", "([a-zA-Z0-9_]{3,16})")).matcher(name);
57 | if(matcher.find()){
58 | return matcher.group(1);
59 | } else {
60 | return plugin.getConfig().getString("aplini.cleanup-name.not-captured", "%nick%")
61 | .replace("%nick%", name)
62 | .replace("%qq%", String.valueOf(qqID));
63 | }
64 | }
65 | return "[Chat2QQ.未启用此功能: aplini.cleanup-name.enabled]";
66 | }
67 |
68 |
69 | // 从文本中匹配 @qqID, 并替换为 @名称
70 | public static String formatQQID(Plugin plugin, String message, long groupID) {
71 | if(plugin.getConfig().getBoolean("aplini.format-qq-id.enabled", true)){
72 |
73 | Matcher matcher = Pattern.compile(plugin.getConfig().getString("aplini.format-qq-id.regular", "(@[0-9]{5,11})")).matcher(message);
74 |
75 | int count = 0;
76 | while (matcher.find()) {
77 | count ++;
78 |
79 | // 获取qqid
80 | long qqID = Long.parseLong(matcher.group().substring(1));
81 | // 获取群名片
82 | String name = getNameFromCache(plugin, groupID, qqID, matcher.group());
83 | // 从群名片匹配出MC标准名称
84 | if(plugin.getConfig().getBoolean("aplini.cleanup-name.enabled",false)){
85 | name = cleanupName(plugin, name, qqID);
86 | }
87 | // 替换
88 | message = message.replace(matcher.group(),
89 | plugin.getConfig().getString("aplini.format-qq-id.format", "format")
90 | .replace("%qq%", String.valueOf(qqID))
91 | .replace("%name%", name)
92 | );
93 |
94 | if(count == plugin.getConfig().getInt("aplini.format-qq-id.max-cycles-num", 10)){
95 | break;
96 | }
97 | }
98 |
99 | }
100 | return message;
101 | }
102 |
103 |
104 | // 初始化群成员缓存
105 | public static void _setGroupCacheAll(Plugin plugin) {
106 | // 新线程
107 | ExecutorService executor = Executors.newSingleThreadExecutor();
108 | executor.submit(() -> {
109 | try {
110 | Map> _group_cache_all = new HashMap<>();
111 |
112 | // 获取开启此功能的群
113 | List configGroupList;
114 | if (plugin.getConfig().getBoolean("aplini.player-cache.use-general-group-ids", true)) {
115 | configGroupList = plugin.getConfig().getLongList("general.group-ids");
116 | } else {
117 | configGroupList = plugin.getConfig().getLongList("aplini.player-cache.group-ids");
118 | }
119 |
120 | // 获取机器人账号
121 | Long botID = plugin.getConfig().getLongList("bot.bot-accounts").get(0);
122 |
123 | // 遍历需要缓存的群
124 | configGroupList.forEach(gid -> {
125 | getLogger().info("[Chat2QQ] 正在缓存群: " + gid);
126 | // 散列表
127 | Map group_cache = new HashMap<>();
128 |
129 | // 获取群数据
130 | JsonArray groupArray;
131 | try {
132 | String jsonString = Files.readString(Paths.get(
133 | plugin.getConfig().getString("aplini.player-cache.mirai-cache-path", "plugins/MiraiMC/MiraiBot/bots/%qq%/cache/contacts/groups/%group%.json")
134 | .replace("%qq%", String.valueOf(botID))
135 | .replace("%group%", String.valueOf(gid))
136 | ));
137 | JsonObject groupJson = new Gson().fromJson(jsonString, JsonObject.class);
138 | groupArray = groupJson.getAsJsonArray("list");
139 | } catch (IOException e) {
140 | getLogger().warning("[Chat2QQ] 读取MiraiMC群数据缓存时出错. botID: " + botID + ", groupID: " + gid);
141 | getLogger().warning("[Chat2QQ] 请检查 MiraiMC 的配置 \"bot.contact-cache.enable-group-member-list-cache\" 是否开启");
142 |
143 | throw new RuntimeException(e);
144 | }
145 |
146 | groupArray.forEach(JsonElement -> {
147 | JsonObject aGroup = (JsonObject) JsonElement;
148 | Long id = aGroup.get("uin").getAsLong();
149 | String name = aGroup.get("nameCard").getAsString();
150 | if (Objects.equals(name, "")) {
151 | if (plugin.getConfig().getBoolean("general.use-nick-if-namecard-null", true)) {
152 | name = aGroup.get("nick").getAsString();
153 | } else {
154 | name = String.valueOf(id);
155 | }
156 | }
157 | group_cache.put(id, name);
158 | });
159 |
160 | // 添加到all
161 | _group_cache_all.put(gid, group_cache);
162 | });
163 |
164 | Chat2QQ.group_cache_all = _group_cache_all;
165 |
166 | getLogger().info("[Chat2QQ] 群成员缓存完成!");
167 | }catch (Exception e){
168 | getLogger().warning("[Chat2QQ] 无法读取来自 MiraiMC 插件的缓存文件, 仅可使用基于玩家消息的缓存");
169 | }
170 | });
171 | executor.shutdown();
172 | }
173 |
174 | // 判断是否为配置中的群
175 | public static boolean isGroupInConfig(Plugin plugin, String funcConfigString, Long groupID){
176 | if(plugin.getConfig().getBoolean(funcConfigString +".use-general-group-ids", true)){
177 | return plugin.getConfig().getLongList("general.group-ids").contains(groupID);
178 | }else{
179 | return plugin.getConfig().getLongList(funcConfigString + ".group-ids").contains(groupID);
180 | }
181 | }
182 |
183 | // PAPI
184 | public static String PAPIString(String inp){
185 | // return PlaceholderAPI.setPlaceholders(new _OfflinePlayer(), inp);
186 | return PlaceholderAPI.setPlaceholders(null, inp);
187 | }
188 |
189 | // 预处理模块 :: 消息替换
190 | public static String pretreatment(Plugin plugin, String configPath, String message){
191 | if(plugin.getConfig().getBoolean(configPath +".enabled",false)){
192 | for(Map, ?> config : plugin.getConfig().getMapList(configPath +".list")){
193 | // 前缀匹配
194 | if(config.get("prefix") != null && message.startsWith(config.get("prefix").toString())){
195 | if(config.get("send") != null){
196 | return "";
197 | }
198 | else if(config.get("to_all") != null){
199 | message = config.get("to_all").toString();
200 | }
201 | else if(config.get("to_replace") != null){
202 | message = message.replace(config.get("prefix").toString(), config.get("to_replace").toString());
203 | }
204 | }
205 |
206 | // 包含
207 | else if(config.get("contain") != null && message.contains(config.get("contain").toString())){
208 | if(config.get("send") != null){
209 | return "";
210 | }
211 | else if(config.get("to_replace") != null){
212 | message = message.replace(config.get("contain").toString(), config.get("to_replace").toString());
213 | }
214 | else if(config.get("to_all") != null){
215 | message = config.get("to_all").toString();
216 | }
217 | }
218 |
219 | // 相等
220 | else if(config.get("equal") != null && Objects.equals(message, config.get("equal"))){
221 | if(config.get("send") != null){
222 | return "";
223 | }
224 | else if(config.get("to_all") != null){
225 | message = config.get("to_all").toString();
226 | }
227 | }
228 |
229 | // 正则匹配
230 | else if(config.get("regular") != null && Pattern.compile(config.get("regular").toString()).matcher(message).find()){
231 | if(config.get("send") != null){
232 | return "";
233 | }
234 | else if(config.get("to_regular") != null){
235 | message = message.replaceAll(config.get("regular").toString(), config.get("to_regular").toString());
236 | }
237 | else if(config.get("to_all") != null){
238 | message = config.get("to_all").toString();
239 | }
240 | }
241 |
242 | // 匹配任何
243 | else if(config.get("any") != null){
244 |
245 | }
246 | }
247 | }
248 |
249 | return message;
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/src/main/java/io/github/aplini/chat2qq/utils/_Commander.java:
--------------------------------------------------------------------------------
1 | package io.github.aplini.chat2qq.utils;
2 |
3 | import org.bukkit.Bukkit;
4 | import org.bukkit.Server;
5 | import org.bukkit.command.CommandSender;
6 | import org.bukkit.permissions.Permission;
7 | import org.bukkit.permissions.PermissionAttachment;
8 | import org.bukkit.permissions.PermissionAttachmentInfo;
9 | import org.bukkit.plugin.Plugin;
10 | import org.jetbrains.annotations.NotNull;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | import java.util.*;
14 |
15 | public class _Commander implements CommandSender {
16 | public List message = new ArrayList<>();
17 |
18 | @Override
19 | public void sendMessage(@NotNull String message) {
20 | this.message.add(message);
21 | }
22 |
23 | @Override
24 | public void sendMessage(@NotNull String[] messages) {
25 | message.addAll(Arrays.asList(messages));
26 | }
27 |
28 | @Override
29 | public void sendMessage(@Nullable UUID sender, @NotNull String message) {
30 | this.message.add(message);
31 | }
32 |
33 | @Override
34 | public void sendMessage(@Nullable UUID sender, @NotNull String... messages) {
35 | message.addAll(Arrays.asList(messages));
36 | }
37 |
38 | @NotNull
39 | @Override
40 | public Server getServer() {
41 | return Bukkit.getConsoleSender().getServer();
42 | }
43 |
44 | @NotNull
45 | @Override
46 | public String getName() {
47 | return "_Chat2QQ_";
48 | }
49 |
50 | @NotNull
51 | @Override
52 | public Spigot spigot() {
53 | return new __spigot(message);
54 | // return Bukkit.getConsoleSender().spigot();
55 | }
56 |
57 | @Override
58 | public boolean isPermissionSet(@NotNull String s) {
59 | return Bukkit.getConsoleSender().isPermissionSet(s);
60 | }
61 |
62 | @Override
63 | public boolean isPermissionSet(@NotNull Permission permission) {
64 | return Bukkit.getConsoleSender().isPermissionSet(permission);
65 | }
66 |
67 | @Override
68 | public boolean hasPermission(@NotNull String s) {
69 | return Bukkit.getConsoleSender().hasPermission(s);
70 | }
71 |
72 | @Override
73 | public boolean hasPermission(@NotNull Permission permission) {
74 | return Bukkit.getConsoleSender().hasPermission(permission);
75 | }
76 |
77 | @NotNull
78 | @Override
79 | public PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String s, boolean b) {
80 | return Bukkit.getConsoleSender().addAttachment(plugin, s, b);
81 | }
82 |
83 | @NotNull
84 | @Override
85 | public PermissionAttachment addAttachment(@NotNull Plugin plugin) {
86 | return Bukkit.getConsoleSender().addAttachment(plugin);
87 | }
88 |
89 | @Override
90 | public PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String s, boolean b, int i) {
91 | return Bukkit.getConsoleSender().addAttachment(plugin, s, b, i);
92 | }
93 |
94 | @Override
95 | public PermissionAttachment addAttachment(@NotNull Plugin plugin, int i) {
96 | return Bukkit.getConsoleSender().addAttachment(plugin, i);
97 | }
98 |
99 | @Override
100 | public void removeAttachment(@NotNull PermissionAttachment permissionAttachment) {
101 |
102 | }
103 |
104 | @Override
105 | public void recalculatePermissions() {
106 |
107 | }
108 |
109 | @NotNull
110 | @Override
111 | public Set getEffectivePermissions() {
112 | return Bukkit.getConsoleSender().getEffectivePermissions();
113 | }
114 |
115 | @Override
116 | public boolean isOp() {
117 | return true;
118 | }
119 |
120 | @Override
121 | public void setOp(boolean b) {
122 |
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/main/java/io/github/aplini/chat2qq/utils/_OfflinePlayer.java:
--------------------------------------------------------------------------------
1 | package io.github.aplini.chat2qq.utils;
2 |
3 | import org.bukkit.*;
4 | import org.bukkit.entity.EntityType;
5 | import org.bukkit.entity.Player;
6 | import org.bukkit.profile.PlayerProfile;
7 | import org.jetbrains.annotations.NotNull;
8 | import org.jetbrains.annotations.Nullable;
9 |
10 | import java.time.Duration;
11 | import java.time.Instant;
12 | import java.util.Date;
13 | import java.util.Map;
14 | import java.util.UUID;
15 |
16 | public class _OfflinePlayer implements OfflinePlayer {
17 | @Override
18 | public boolean isOnline() {
19 | return false;
20 | }
21 |
22 | @Nullable
23 | @Override
24 | public String getName() {
25 | return "_Chat2QQ_";
26 | }
27 |
28 | @NotNull
29 | @Override
30 | public UUID getUniqueId() {
31 | return null;
32 | }
33 |
34 | /**
35 | * Gets a copy of the player's profile.
36 | *
37 | * If the player is online, the returned profile will be complete.
38 | * Otherwise, only the unique id is guaranteed to be present. You can use
39 | * {@link PlayerProfile#update()} to complete the returned profile.
40 | *
41 | * @return the player's profile
42 | */
43 | @NotNull
44 | @Override
45 | public PlayerProfile getPlayerProfile() {
46 | return null;
47 | }
48 |
49 | @Override
50 | public boolean isBanned() {
51 | return false;
52 | }
53 |
54 | /**
55 | * Adds this user to the {@link ProfileBanList}. If a previous ban exists, this will
56 | * update the entry.
57 | *
58 | * @param reason reason for the ban, null indicates implementation default
59 | * @param expires date for the ban's expiration (unban), or null to imply
60 | * forever
61 | * @param source source of the ban, null indicates implementation default
62 | * @return the entry for the newly created ban, or the entry for the
63 | * (updated) previous ban
64 | */
65 | @Nullable
66 | @Override
67 | public BanEntry ban(@Nullable String reason, @Nullable Date expires, @Nullable String source) {
68 | return null;
69 | }
70 |
71 | /**
72 | * Adds this user to the {@link ProfileBanList}. If a previous ban exists, this will
73 | * update the entry.
74 | *
75 | * @param reason reason for the ban, null indicates implementation default
76 | * @param expires instant for the ban's expiration (unban), or null to imply
77 | * forever
78 | * @param source source of the ban, null indicates implementation default
79 | * @return the entry for the newly created ban, or the entry for the
80 | * (updated) previous ban
81 | */
82 | @Nullable
83 | @Override
84 | public BanEntry ban(@Nullable String reason, @Nullable Instant expires, @Nullable String source) {
85 | return null;
86 | }
87 |
88 | /**
89 | * Adds this user to the {@link ProfileBanList}. If a previous ban exists, this will
90 | * update the entry.
91 | *
92 | * @param reason reason for the ban, null indicates implementation default
93 | * @param duration how long the ban last, or null to imply
94 | * forever
95 | * @param source source of the ban, null indicates implementation default
96 | * @return the entry for the newly created ban, or the entry for the
97 | * (updated) previous ban
98 | */
99 | @Nullable
100 | @Override
101 | public BanEntry ban(@Nullable String reason, @Nullable Duration duration, @Nullable String source) {
102 | return null;
103 | }
104 |
105 | @Override
106 | public boolean isWhitelisted() {
107 | return false;
108 | }
109 |
110 | @Override
111 | public void setWhitelisted(boolean value) {
112 |
113 | }
114 |
115 | @Nullable
116 | @Override
117 | public Player getPlayer() {
118 | return null;
119 | }
120 |
121 | @Override
122 | public long getFirstPlayed() {
123 | return 0;
124 | }
125 |
126 | @Override
127 | public long getLastPlayed() {
128 | return 0;
129 | }
130 |
131 | @Override
132 | public boolean hasPlayedBefore() {
133 | return false;
134 | }
135 |
136 | @Nullable
137 | @Override
138 | public Location getBedSpawnLocation() {
139 | return null;
140 | }
141 |
142 | @Nullable
143 | @Override
144 | public Location getRespawnLocation() {
145 | return null;
146 | }
147 |
148 | @Override
149 | public void incrementStatistic(@NotNull Statistic statistic) throws IllegalArgumentException {
150 |
151 | }
152 |
153 | @Override
154 | public void decrementStatistic(@NotNull Statistic statistic) throws IllegalArgumentException {
155 |
156 | }
157 |
158 | @Override
159 | public void incrementStatistic(@NotNull Statistic statistic, int amount) throws IllegalArgumentException {
160 |
161 | }
162 |
163 | @Override
164 | public void decrementStatistic(@NotNull Statistic statistic, int amount) throws IllegalArgumentException {
165 |
166 | }
167 |
168 | @Override
169 | public void setStatistic(@NotNull Statistic statistic, int newValue) throws IllegalArgumentException {
170 |
171 | }
172 |
173 | @Override
174 | public int getStatistic(@NotNull Statistic statistic) throws IllegalArgumentException {
175 | return 0;
176 | }
177 |
178 | @Override
179 | public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
180 |
181 | }
182 |
183 | @Override
184 | public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
185 |
186 | }
187 |
188 | @Override
189 | public int getStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
190 | return 0;
191 | }
192 |
193 | @Override
194 | public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) throws IllegalArgumentException {
195 |
196 | }
197 |
198 | @Override
199 | public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) throws IllegalArgumentException {
200 |
201 | }
202 |
203 | @Override
204 | public void setStatistic(@NotNull Statistic statistic, @NotNull Material material, int newValue) throws IllegalArgumentException {
205 |
206 | }
207 |
208 | @Override
209 | public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
210 |
211 | }
212 |
213 | @Override
214 | public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
215 |
216 | }
217 |
218 | @Override
219 | public int getStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
220 | return 0;
221 | }
222 |
223 | @Override
224 | public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) throws IllegalArgumentException {
225 |
226 | }
227 |
228 | @Override
229 | public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) {
230 |
231 | }
232 |
233 | @Override
234 | public void setStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int newValue) {
235 |
236 | }
237 |
238 | /**
239 | * Gets the player's last death location.
240 | *
241 | * @return the last death location if it exists, otherwise null.
242 | */
243 | @Nullable
244 | @Override
245 | public Location getLastDeathLocation() {
246 | return null;
247 | }
248 |
249 | @Nullable
250 | @Override
251 | public Location getLocation() {
252 | return null;
253 | }
254 |
255 | @NotNull
256 | @Override
257 | public Map serialize() {
258 | return null;
259 | }
260 |
261 | @Override
262 | public boolean isOp() {
263 | return true;
264 | }
265 |
266 | @Override
267 | public void setOp(boolean value) {
268 |
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/src/main/java/io/github/aplini/chat2qq/utils/__spigot.java:
--------------------------------------------------------------------------------
1 | package io.github.aplini.chat2qq.utils;
2 |
3 | import org.bukkit.command.CommandSender;
4 | import org.jetbrains.annotations.NotNull;
5 | import org.jetbrains.annotations.Nullable;
6 |
7 | import java.util.List;
8 | import java.util.UUID;
9 |
10 | public class __spigot extends CommandSender.Spigot {
11 | List message;
12 | public __spigot(List message) {
13 | this.message = message;
14 | }
15 |
16 | @Deprecated
17 | public void sendMessage(@NotNull net.md_5.bungee.api.chat.BaseComponent component) {
18 | this.message.add(component.toLegacyText());
19 | }
20 |
21 | @Deprecated
22 | public void sendMessage(@NotNull net.md_5.bungee.api.chat.BaseComponent... components) {
23 | for(net.md_5.bungee.api.chat.BaseComponent component : components){
24 | this.message.add(component.toLegacyText());
25 | }
26 | }
27 |
28 | @Deprecated
29 | public void sendMessage(@Nullable UUID sender, @NotNull net.md_5.bungee.api.chat.BaseComponent component) {
30 | this.message.add(component.toLegacyText());
31 | }
32 |
33 | @Deprecated
34 | public void sendMessage(@Nullable UUID sender, @NotNull net.md_5.bungee.api.chat.BaseComponent... components) {
35 | for(net.md_5.bungee.api.chat.BaseComponent component : components){
36 | this.message.add(component.toLegacyText());
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/io/github/aplini/chat2qq/utils/renderGroupMessage.java:
--------------------------------------------------------------------------------
1 | package io.github.aplini.chat2qq.utils;
2 |
3 | import me.dreamvoid.miraimc.api.MiraiMC;
4 | import me.dreamvoid.miraimc.bukkit.event.message.passive.MiraiGroupMessageEvent;
5 | import net.md_5.bungee.api.chat.HoverEvent;
6 | import net.md_5.bungee.api.chat.TextComponent;
7 | import net.md_5.bungee.api.chat.hover.content.Text;
8 | import org.bukkit.plugin.Plugin;
9 |
10 | import static io.github.aplini.chat2qq.utils.Util.*;
11 |
12 | public class renderGroupMessage {
13 |
14 | // 渲染 message
15 | public static String _renderMessage(Plugin plugin, String message) {
16 | // 预处理模块
17 | message = pretreatment(plugin, "aplini.pretreatment", message);
18 |
19 | // 预设的格式调整功能. 是否删除 %message% 消息 中的格式化字符
20 | if(plugin.getConfig().getBoolean("aplini.other-format-presets.render-message_format-code",false)){
21 | message = message.replaceAll("§[a-z0-9]", "");
22 | }
23 |
24 | // 预设的格式调整功能. 删除 %message% 前后的空格和换行
25 | if(plugin.getConfig().getBoolean("aplini.other-format-presets.message-trim",true)){
26 | message = message.trim();
27 | }
28 |
29 | return message;
30 | }
31 |
32 |
33 |
34 | // 回复消息
35 | public static String getReplyVar(Plugin plugin, MiraiGroupMessageEvent e) {
36 | if(e.getQuoteReplyMessage() != null){
37 | return plugin.getConfig().getString("aplini.reply-message.var", "[reply] ")
38 | .replace("%c_name%", cleanupName(
39 | plugin,
40 | getNameFromCache(plugin, e.getGroupID(), e.getQuoteReplySenderID(), String.valueOf(e.getQuoteReplySenderID())),
41 | e.getQuoteReplySenderID()))
42 | .replace("%qq%", String.valueOf(e.getQuoteReplySenderID()))
43 | .replace("%_/n_%", "\n");
44 | }
45 | return "";
46 | }
47 |
48 |
49 | // 可读的消息
50 | public static String [] renderMessage1(Plugin plugin, MiraiGroupMessageEvent e) {
51 |
52 | String [] message = new String [4];
53 | message[0] = ""; // 消息类型
54 | message[1] = e.getMessage(); // 源消息
55 | message[2] = ""; // 经过格式化后, 内容可见的消息
56 | message[3] = ""; // 可能不包含可见消息的显示消息, 用于控制台以及配合JSON消息
57 |
58 | // no = 不发送
59 |
60 | // 群聊天前缀 (聊天需要带有指定前缀才能发送到服务器)
61 | if(plugin.getConfig().getBoolean("general.requite-special-word-prefix.enabled",false)){
62 | boolean allowPrefix = false;
63 | // - "#"
64 | for(String prefix : plugin.getConfig().getStringList("general.requite-special-word-prefix.prefix")){
65 | if(message[1].startsWith(prefix)){
66 | allowPrefix = true;
67 | // 移除消息中的前缀
68 | message[1] = message[1].substring(prefix.length());
69 | break;
70 | }
71 | }
72 | if(! allowPrefix){
73 | message[0] = "no";
74 | return message;
75 | }
76 | }
77 |
78 | // 当群名片不存在时是否尝试获取昵称
79 | String name = e.getSenderNameCard();
80 | if(name.equalsIgnoreCase("") && plugin.getConfig().getBoolean("general.use-nick-if-namecard-null",true)){
81 | name = e.getSenderName();
82 | }
83 | // 预设的格式调整功能. 是否删除 %nick% 群名片 中的格式化字符
84 | if(plugin.getConfig().getBoolean("aplini.other-format-presets.render-nick_format-code",true)){
85 | name = name.replaceAll("§[a-z0-9]", "");
86 | }
87 |
88 |
89 | // 经过格式化后, 内容可见的消息
90 | message[2] = message[1];
91 |
92 |
93 | // 引用回复
94 | // 如果是回复消息, 则删除消息开头重复的 @qqID
95 | if(e.getQuoteReplyMessage() != null && plugin.getConfig().getBoolean("aplini.reply-message.del-duplicates-at",true)){
96 | String atField = "@"+ e.getQuoteReplySenderID();
97 | if(message[2].startsWith(atField)){
98 | message[2] = message[2].substring(atField.length());
99 | }
100 | }
101 |
102 | // 预处理
103 | message[2] = _renderMessage(plugin, message[2]);
104 | if(message[2].equals("")){
105 | message[0] = "no";
106 | return message;
107 | }
108 |
109 |
110 | // 可能不包含可见消息的显示消息
111 | message[3] = message[2];
112 |
113 |
114 | // 预设的格式调整功能. 更好的多行消息
115 | if(plugin.getConfig().getBoolean("aplini.other-format-presets.multiline-message.enabled",true) && message[3].contains("\n")){
116 | String _l0 = plugin.getConfig().getString("aplini.other-format-presets.multiline-message.line-0", "line-0");
117 | String _l1 = plugin.getConfig().getString("aplini.other-format-presets.multiline-message.line-prefix", "line-prefix");
118 | message[3] = _l0 + "\n" + _l1 + message[3].replace("\n", "\n" + _l1);
119 | }
120 |
121 | // 预设的格式调整功能. 聊天消息过长时转换为悬浮文本
122 | if(message[2].length() > plugin.getConfig().getInt("aplini.other-format-presets.long-message.condition-length", 210) ||
123 | message[2].contains("\n") &&
124 | message[2].length() - message[2].replace("\n","").length() >
125 | plugin.getConfig().getInt("aplini.other-format-presets.long-message.condition-line_num", 6)){
126 | message[0] = "lm";
127 | message[3] = plugin.getConfig().getString("aplini.other-format-presets.long-message.message");
128 |
129 | // 删除消息中多余的换行, 用于解决 "预设的格式调整功能. 更好的多行消息" 造成的消息开头多一个空行
130 | message[2] = message[2].trim();
131 | }
132 |
133 |
134 | // 消息模板路径
135 | String message2_config_path;
136 | // Mirai 内置QQ绑定
137 | if(plugin.getConfig().getBoolean("general.use-miraimc-bind",false) && MiraiMC.getBind(e.getSenderID()) != null){
138 | message2_config_path = plugin.getConfig().getString("general.special-bind."+ e.getGroupID(),
139 | plugin.getConfig().getString("general.bind-chat-format", "message"));
140 | }else{
141 | message2_config_path = plugin.getConfig().getString("general.special."+ e.getGroupID(),
142 | plugin.getConfig().getString("general.in-game-chat-format", "message"));
143 | }
144 |
145 | String message3 = message[3];
146 | message[3] = message2_config_path
147 | .replace("%groupname%",e.getGroupName())
148 | .replace("%groupid%",String.valueOf(e.getGroupID()))
149 | .replace("%qq%",String.valueOf(e.getSenderID()))
150 | .replace("%nick%",name)
151 | .replace("%regex_nick%", cleanupName(plugin, name, e.getSenderID())) // aplini.cleanup-name
152 | .replace("%_reply_%", getReplyVar(plugin, e))
153 | .replace("%message%", formatQQID(plugin, message3, e.getGroupID()));
154 |
155 | return message;
156 | }
157 |
158 | // JSON消息
159 | public static TextComponent renderMessage2(Plugin plugin, String [] message, MiraiGroupMessageEvent e) {
160 |
161 | // 转换为JSON文本
162 | TextComponent formatText = new TextComponent(message[3]);
163 |
164 | // 预设的格式调整功能. 聊天消息过长时转换为悬浮文本
165 | if(message[0].equals("lm")){
166 | // 设置悬浮文本
167 | formatText.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(message[2])));
168 | }
169 |
170 | // 如果是回复消息
171 | if(e.getQuoteReplyMessage() != null){
172 | // 创建回复消息的悬浮文本
173 | String replyMessage = plugin.getConfig().getString("aplini.reply-message.message", "[引用回复]")
174 | .replace("%c_name%", cleanupName(plugin,
175 | getNameFromCache(plugin, e.getGroupID(), e.getQuoteReplySenderID(), String.valueOf(e.getQuoteReplySenderID())),
176 | e.getQuoteReplySenderID()))
177 | .replace("%qq%", String.valueOf(e.getQuoteReplySenderID()))
178 | .replace("%_/n_%", "\n")
179 | .replace("%message%", formatQQID(plugin, _renderMessage(plugin, e.getQuoteReplyMessage()), e.getGroupID()))
180 | .replace("%main_message%", message[2]); // 跳过消息过长部分
181 | // 设置悬浮文本
182 | formatText.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(replyMessage)));
183 | }
184 |
185 | return formatText;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/src/main/resources/config.yml:
--------------------------------------------------------------------------------
1 |
2 | # 游戏内配置
3 | # QQ -> MC 的消息
4 | general:
5 | # 转发哪些QQ群的消息
6 | group-ids:
7 | - 1000000
8 | - 1000001
9 |
10 | # 群聊天前缀 (聊天需要带有指定前缀才能发送到服务器)
11 | requite-special-word-prefix:
12 | enabled: false
13 | prefix:
14 | - '#'
15 |
16 | # 当群名片不存在时是否尝试获取昵称
17 | use-nick-if-namecard-null: true
18 |
19 | # QQ群消息广播到游戏内聊天的格式 格式化代码: §
20 | # %groupname% - 群名称
21 | # %groupid% - 群号
22 | # %nick% - 发送者群名片
23 | # %regex_nick% - 使用正则匹配到的名称, 需要开启 aplini.cleanup-name 模块
24 | # %qq% - 发送者QQ号
25 | # %message% - 消息内容, 支持预处理模块 aplini.pretreatment
26 | # %_reply_% - 如果是回复消息..., 配置在 aplini.reply-message 模块
27 | in-game-chat-format: '§f[§7%nick%§r§f] %_reply_%§7%message%'
28 | # 为每个群使用不同的格式, 如果没有则使用上方的 in-game-chat-format
29 | special:
30 | 1000000: '§f[§7主群 %nick%§r§f] %_reply_%§7%message%'
31 | 1000001: '§7[外群 %nick%] %_reply_%%message%'
32 |
33 | # 启用 MiraiMC 内置的QQ绑定
34 | use-miraimc-bind: false
35 | # 已绑定玩家的广播消息格式
36 | bind-chat-format: '§f[§7%nick%§r§f] %_reply_%§7%message%'
37 | # 为每个群使用不同的格式, 如果没有则使用上方的 bind-chat-format
38 | special-bind:
39 | 1000000: '§f[§7主群 %nick%§r§f] %_reply_%§7%message%'
40 |
41 |
42 |
43 | # 机器人配置
44 | # MC -> QQ 的消息
45 | bot:
46 | # 使用哪些QQ号处理消息
47 | # 只能添加一个
48 | bot-accounts:
49 | - 2000000
50 |
51 | # 将消息转发到那些QQ群
52 | group-ids:
53 | - 1000000
54 |
55 | # 玩家在以下世界中聊天才会被转发
56 | available-worlds:
57 | #- world
58 | # 将以上配置作为黑名单, 玩家不在以上世界中聊天才会被转发
59 | available-worlds-use-as-blacklist: true
60 |
61 | # 游戏聊天前缀 (聊天需要带有指定前缀才能发送到QQ群)
62 | requite-special-word-prefix:
63 | enabled: true
64 | prefix:
65 | - '#'
66 |
67 | # 是否转发被其他插件取消过的聊天消息事件, 用于修复一些兼容性问题
68 | ignoreCancelled: false
69 |
70 | # 服务器消息发送到QQ群的格式
71 | # %player% - 玩家名称
72 | # %message% - 消息内容
73 | group-chat-format: '[%player%] %message%'
74 |
75 |
76 | # 是否发送玩家进出服务器的消息
77 | # %player% - 玩家显示昵称
78 | send-player-join-quit-message: false
79 | # 加入
80 | player-join-message: '%player% 进入服务器'
81 | # 退出
82 | player-quit-message: '%player% 离开服务器'
83 | # 防刷屏, 在此时间内多次进出服务器不会发送消息
84 | player-join-quit-message-interval: 0
85 |
86 |
87 |
88 | # 黑名单, 可用于添加其他QQ机器人
89 | # 优先级大于上方配置
90 | blacklist:
91 | # 不转发以下QQ号的聊天消息
92 | qq:
93 | #- 2000001
94 |
95 | # 不转发以下玩家名的聊天消息
96 | player:
97 | #- playerName
98 |
99 |
100 | # ############### #
101 | # 以下为功能模块配置 #
102 | # ############### #
103 |
104 | aplini:
105 |
106 | ## 1
107 | # 在QQ群中运行指令 [需要单独添加QQ群]
108 | # 此模块不处理黑名单 blacklist
109 | run-command:
110 | enabled: false
111 | # 启用的 QQ群
112 | qq-group:
113 | - 1000001
114 |
115 | # 指令前缀, 可以是多个字符, 比如 "~$"
116 | command-prefix: '/'
117 | # 指令最大长度 (不包括指令前缀)
118 | command-max-length: 255
119 | # 获取指令的正则表达式, 当第一个捕获组的内容与指令白名单中的匹配时则允许运行 (不带斜杠或前缀)
120 | regex-command-main: '^([^ ]+)'
121 | # 判断指令返回为空的正则, 匹配多行文本. (经过 pretreatment-command-message 处理后)
122 | return-isNull: '^\s*$'
123 | # 是否将主命令转换为小写再执行
124 | always-lowercase: false
125 |
126 | # 是否发送指令的输出, 关闭可提高性能或解决一些兼容性问题
127 | return: true
128 | # 等待指令运行多长时间再将结果发送到QQ群 (毫秒), 需要开启 run-command.return
129 | # 如果你遇到了一些提前输出类似 "正在运行...请稍等" 消息的插件, 可以在 pretreatment-command-message 中配置完全删除这条消息. 然后 return-sleep-min 保持不变 :)
130 | return-sleep-min: 14 # 最小等待时间
131 | return-sleep-max: 5346 # 最大等待时间, 如果一些长耗时指令没有输出请增大此值
132 | return-sleep-sampling-interval: 172 # 输出内容检查间隔, 如果经常执行长耗时指令可以增大此值
133 | # 是否将指令的输出打印到控制台和日志
134 | return-log: true
135 |
136 | # 执行不在白名单中的指令时发送返回消息
137 | message-miss: '未命中的指令'
138 | # 运行无返回指令的消息
139 | message-no-out: '运行无返回指令'
140 |
141 | # 设置各组可执行的主命令白名单 (不带斜杠或前缀)
142 | # 权限更高的用户将可以使用更低的用户的指令
143 | # 如果添加一条 ___ALL_COMMAND___ 作为指令, 则表示此组可以使用所有指令, 此功能请勿随意使用 !
144 | group:
145 | # permission_ 是 MiraiMC 获取到的权限数字, 以后更新了其他权限只需要以此格式添加即可使用
146 | permission_2: # 群主
147 | #- chat2qq
148 | permission_1: # 管理员
149 | #- spark
150 | permission_0: # 成员
151 | #- list
152 | #- tps
153 |
154 | # 特殊指令配置
155 | special:
156 | no-return: # 这些指令始终不输出消息
157 | #- plugins
158 | #- version
159 |
160 |
161 | ## 2
162 | # 从 群名片(%nick%) 中匹配 MC 可用的游戏名称
163 | # 添加变量: %regex_nick% - 使用正则匹配到的名称, 需要开启 cleanup-name 功能
164 | cleanup-name:
165 | enabled: false
166 | # 程序取第一个捕获组的结果
167 | regex: '([a-zA-Z0-9_]{3,16})'
168 | # 如果匹配不到, 则使用以下字符串
169 | # %nick% - 群名片
170 | # %qq% - qq号
171 | not-captured: '%nick%'
172 |
173 |
174 | ## 3
175 | # 预处理 %message% 中的消息
176 | pretreatment:
177 | enabled: true
178 | # **使用方法**
179 | # list:
180 | # - 匹配方式: prefix (前缀匹配), 处理方式: to_all, to_replace
181 | # contain (包含), 处理方式: to_all, to_replace
182 | # equal (完全相等), 处理方式: to_all
183 | # regular (正则匹配), 处理方式: to_all, to_regular
184 | #
185 | # 处理方式: to_all (替换整条消息)
186 | # to_replace (替换匹配到的部分)
187 | # to_regular (使用正则替换, 可使用正则变量)
188 | #
189 | # 是否发送: send (填写 send 配置将取消转发送匹配到的消息, 不需要时请忽略)
190 |
191 | # 示例配置, 默认配置了一些可能有用的功能:
192 | list:
193 |
194 | # 群公告, JSON
195 | - prefix: '{"app":"com.tencent.mannounce"'
196 | to_all: '[群公告]'
197 |
198 | # 视频, 字符串
199 | - prefix: '你的QQ暂不支持查看视频短片'
200 | to_all: '[视频]'
201 |
202 | # 使中括号与文本的前后始终有空格
203 | - regular: '\[([^\]]+)\]([^\s])'
204 | to_regular: '[$1] $2'
205 | - regular: '([^\s])\[([^\]]+)\]'
206 | to_regular: '$1 [$2]'
207 |
208 | # 转发消息使用前缀, 在群中使用 # 前缀将改变消息格式
209 | - regular: '^\s*(?:#|#)'
210 | to_regular: '§7> §f'
211 |
212 | # 示例: 取消发送包含此内容的消息
213 | #- contain: '此内容'
214 | # send: false
215 |
216 |
217 | ## 3.1
218 | # 按行预处理指令返回消息, 用于处理返回到QQ群的消息
219 | pretreatment-command-message:
220 | enabled: true
221 | # 使用方法: 如上
222 | list:
223 | # 删除格式化字符
224 | - regular: '§[a-z0-9]'
225 | to_regular: ''
226 |
227 | # 示例: co插件翻页消息处理
228 | #- regular: '◀? ?第 (.*) 页 ▶? ?\((.*)\)'
229 | # to_regular: '第 $1 页, 使用 /co page <页码> 翻页'
230 |
231 |
232 | ## 3.2
233 | # 按多行文本预处理指令返回消息
234 | # 可使用占位符:
235 | # - %command% :: 用户运行的指令原文(不带斜杠/前缀)
236 | # - %time% :: 指令运行耗时
237 | # - %qq% :: 执行指令的qq号
238 | # - %group% :: 执行指令的群号
239 | pretreatment-command-message-all:
240 | enabled: false
241 | enabled-placeholder: false # 关闭占位符可提高性能
242 | # 使用方法: 如上
243 | list:
244 | # 示例: 显示指令运行时间, 需要开启占位符
245 | #- regular: '([\s\S]+)'
246 | # to_regular: '$1\n - 运行耗时: %time%ms'
247 |
248 |
249 | ## 4
250 | # 预设的格式调整功能
251 | other-format-presets:
252 | # 是否删除 %message% 消息 中的格式化字符
253 | render-message_format-code: false
254 | # 删除 %message% 消息 前后的空格和空行
255 | message-trim: true
256 | # 是否删除 %nick% 群名片 中的格式化字符
257 | render-nick_format-code: true
258 |
259 | # 聊天消息过长时转换为悬浮文本
260 | long-message:
261 | enabled: true
262 | # 以下任意一个条件成立时被判定为长消息, 若需取消一个, 请改为很大的数
263 | # 条件1: 消息长度达到此值
264 | condition-length: 210
265 | # 条件2: 换行数量达到此值, 在 message-trim 之后运行
266 | condition-line_num: 6
267 | # 显示为
268 | message: '§f[§7长消息§f]'
269 |
270 | # 是否启用 "更好的多行消息"
271 | multiline-message:
272 | enabled: true
273 | line-0: '' # [多行消息]
274 | line-prefix: ' '
275 |
276 | # 是否将聊天消息转发到控制台/日志
277 | message-to-log: true
278 |
279 |
280 | ## 5
281 | # 引用回复
282 | # 添加变量: %_reply_%
283 | # 如果是回复消息, 则为变量赋值并为消息添加悬浮文本框用于显示内容. 可以将鼠标悬停在消息上查看回复的内容
284 | reply-message:
285 | # 可用变量:
286 | # %qq% - 被回复的消息的发送者QQ号
287 | # %c_name% - 群名片 - 需要开启 aplini.format-qq-id
288 | var: '§f[§7回复 @%c_name%§f] '
289 |
290 | # 可用变量:
291 | # %_/n_% - 换行
292 | # %qq% - 被回复的消息的发送者QQ号
293 | # %c_name% - 群名片 - 需要开启 aplini.format-qq-id
294 | # %message% - 回复内容
295 | # %main_message% - 当前消息内容
296 | message: '§f[§7引用 @%c_name%§f]%_/n_%§7%message%§r%_/n_%%_/n_%§f——%main_message%'
297 |
298 | # 删除重复@ :: 如果引用回复对象等于消息开头的@对象, 则删除消息开头的 @
299 | del-duplicates-at: true
300 |
301 |
302 | ## 6
303 | # 发送消息的指令
304 | # /qchat <消息> - 使用此指令
305 | qchat:
306 | # 使用上方 general.group-ids 中配置的群
307 | use-general-group-ids: true
308 | # 消息转发到哪些群, 需要 use-general-group-ids: false
309 | group-ids:
310 | - 1000000
311 | # 如果是玩家使用指令
312 | player:
313 | # 转发到QQ群的格式
314 | # %name% - 玩家名称
315 | # %message% - 消息
316 | qq-format: '[%name%] %message%'
317 | # 是否同时将消息广播到MC服务器
318 | mc-broadcast: true
319 | # 广播到MC服务器的
320 | mc-format: '§f[§7%name%§f] §7%message%'
321 | # 如果是控制台或插件使用指令, 同时绕过关键词和玩家黑名单
322 | console:
323 | # %message% - 消息
324 | qq-format: '%message%'
325 |
326 |
327 | ## 7
328 | # [前置] 群成员信息缓存
329 | # ! 需要开启 MiraiMC 配置中的 bot.contact-cache.enable-group-member-list-cache
330 | player-cache:
331 | # 在机器人登录和服务器启动时运行此程序
332 | enabled: true
333 | # 在玩家群名片修改时更新缓存
334 | auto-update: true
335 | # 在玩家发送消息时更新缓存
336 | auto-update-form-msg: true
337 | # 群名片修改时发出日志
338 | auto-update-log: true
339 | # 使用上方 general.group-ids 中配置的群
340 | use-general-group-ids: true
341 | # 缓存哪些群, 需要 use-general-group-ids: false
342 | group-ids:
343 | - 1000000
344 | # MiraiMC 群缓存文件路径, 如果你修改了插件目录相关的配置, 才需要修改它
345 | # %qq% - 机器人账号
346 | # %group% - 群号
347 | mirai-cache-path: "plugins/MiraiMC/MiraiBot/bots/%qq%/cache/contacts/groups/%group%.json"
348 |
349 |
350 | ## 8
351 | # 将 %message% 中的 @qqID 替换为 @名称
352 | # 需要开启前置: aplini.player-cache
353 | format-qq-id:
354 | enabled: true
355 | # 用于匹配 @qqID 的正则
356 | regular: '(@[0-9]{5,11})'
357 | # 格式
358 | # %qq% - qq号
359 | # %name% - 名称
360 | format: '§f[§7@%name%§f]§7'
361 | # 一条消息最多匹配几次, 防止刷屏浪费性能
362 | max-cycles-num: 10
363 |
364 |
365 | ## 9
366 | # 事件任务
367 | event-func:
368 | # enable 修改后需要重启服务器
369 | enable: false
370 | # 使用上方 general.group-ids 中配置的群
371 | use-general-group-ids: true
372 | # 启用在哪些群, 需要 use-general-group-ids: false
373 | group-ids:
374 | - 1000000
375 |
376 | # 每个事件可用的任务不同, 这里列出了所有任务的使用方法:
377 | # - command: 'command' - 发送指令
378 |
379 | # - message-text: '消息' - 向事件来源发送消息, 群 或 好友/私聊
380 |
381 | # - message-group: 1000000 - 向指定群发送消息
382 | # message-text: '消息'
383 |
384 | # - message-friend: 2000003 - 向指定好友发送消息
385 | # message-text: '消息'
386 |
387 | MiraiMemberJoinEvent: # 群成员加入
388 | # 可使用: command, message-text, message-group
389 | # 一个事件中可添加多个相同或不相同的任务, 就像这样:
390 | #- message-text: '欢迎'
391 | #- command: 'tps'
392 | #- command: 'mspt'
393 | #- message-group: 1000000
394 | # message-text: '消息'
395 |
396 | MiraiMemberLeaveEvent: # 成员退出
397 | # 可使用: command, message-text, message-group
398 |
399 |
400 | ## 10
401 | # 自动回复
402 | # 当QQ群中的消息匹配时发送自定义消息
403 | auto-response:
404 | enable: true
405 | # 使用上方 general.group-ids 中配置的群
406 | use-general-group-ids: true
407 | # 回复哪些群的消息, 需要 use-general-group-ids: false
408 | group-ids:
409 | - 1000000
410 | # 为此功能启用PAPI, 需要安装PAPI插件
411 | enable-papi: false
412 |
413 | # **使用方法**
414 | # list:
415 | # - 匹配方式: prefix (前缀匹配)
416 | # contain (包含)
417 | # equal (完全相等)
418 | # regular (正则匹配, send 中可使用正则变量)
419 | # send (发送的消息内容)
420 | #
421 | # > 正则的性能较差, 请尽量避免使用很多正则
422 | # !! 请小心使用正则拼接PAPI变量, 如果正则设计有问题则可能出现注入漏洞 !!
423 | # - 提示: 应指定匹配的字符范围和最小最大次数, 要绝对的防止输入PAPI变量的保留符号: %
424 | # - 比如: - regular: '^\#ping ([a-zA-Z0-9_]{3,16})$'
425 | # send: '$1 的延迟为: %player_ping_$1%ms'
426 | # 示例配置, 默认配置了一些可能有用的功能:
427 | list:
428 |
429 | # 使用PAPI获取在线玩家数量, 需要启用 aplini.auto-response.enable-papi
430 | # PlayerList: /papi ecloud download playerlist
431 | - equal: '#list'
432 | send: '在线玩家: [%playerlist_online,normal,yes,amount%] \n%playerlist_online,normal,yes,list%'
433 |
434 | # 使用PAPI获取服务器TPS, 需要启用 aplini.auto-response.enable-papi
435 | # Server: /papi ecloud download Server
436 | - equal: '#tps'
437 | send: 'TPS [1m, 5m, 15m]: %server_tps_1% / %server_tps_5% / %server_tps_15%'
438 |
439 | # 指令列表
440 | - equal: '#help'
441 | send: '指令列表:
442 | \n - #list - 显示在线玩家列表
443 | \n - #tps - 显示服务器TPS'
444 |
445 | # @一个QQ号时发送消息
446 | - contain: '@2000000'
447 | send: 'OwO'
448 |
449 |
450 | # <- 至此, 您已经完成了所有配置, 部分功能使用 /chat2qq reload 重载插件即可应用 uwu
451 |
--------------------------------------------------------------------------------
/src/main/resources/plugin.yml:
--------------------------------------------------------------------------------
1 | name: Chat2QQ
2 | main: io.github.aplini.chat2qq.Chat2QQ
3 | version: '${project.version}'
4 | api-version: 1.18
5 | folia-supported: true
6 | prefix: MiraiMC
7 | author: ApliNi
8 |
9 | softdepend:
10 | - PlaceholderAPI
11 |
12 | depend:
13 | - MiraiMC
14 |
15 | commands:
16 | qchat:
17 | description: 发送聊天消息到QQ群
18 | permission: chat2qq.command.qchat
19 |
20 | chat2qq:
21 | description: Chat2QQ 插件主命令
22 |
23 | permissions:
24 | chat2qq.qq.receive:
25 | description: 允许收到来自QQ群的消息
26 | default: true
27 |
28 | chat2qq.chat.requite:
29 | description: 允许使用前缀符号转发消息到QQ群
30 | default: true
31 |
32 | chat2qq.join.silent:
33 | description: 允许悄悄加入服务器
34 | default: false
35 |
36 | chat2qq.quit.silent:
37 | description: 允许悄悄离开服务器
38 | default: false
39 |
40 | chat2qq.command.qchat:
41 | description: 允许使用 /qchat
42 | default: op
43 |
44 | chat2qq.command.chat2qq:
45 | description: 允许使用 /chat2qq
46 | default: op
47 |
48 | chat2qq:.command.setgroupcacheall:
49 | description: 允许使用 /chat2qq setgroupcacheall
50 | default: op
51 |
--------------------------------------------------------------------------------