[Change] 调整CGJ包结构, 将机器人平台相关类单独存放在framework包内;

This commit is contained in:
2020-04-28 23:15:55 +08:00
parent 6824b12e8f
commit 22a113ef38
10 changed files with 26 additions and 16 deletions

View File

@ -0,0 +1,27 @@
package net.lamgc.cgj.bot.framework.coolq;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import net.lz1998.cq.CQGlobal;
import net.lz1998.cq.EnableCQ;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@EnableCQ
public class CQConfig {
public static void init() {
CQGlobal.pluginList.add(CQPluginMain.class);
CQGlobal.executor = new ThreadPoolExecutor(
(int) Math.ceil(Runtime.getRuntime().availableProcessors() / 2F),
Runtime.getRuntime().availableProcessors(),
25, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(512),
new ThreadFactoryBuilder()
.setNameFormat("Plugin-ProcessThread-%d")
.build()
);
}
}

View File

@ -0,0 +1,51 @@
package net.lamgc.cgj.bot.framework.coolq;
import net.lamgc.cgj.bot.event.BotEventHandler;
import net.lamgc.cgj.bot.framework.coolq.message.SpringCQMessageEvent;
import net.lamgc.utils.event.EventHandler;
import net.lz1998.cq.event.message.CQDiscussMessageEvent;
import net.lz1998.cq.event.message.CQGroupMessageEvent;
import net.lz1998.cq.event.message.CQMessageEvent;
import net.lz1998.cq.event.message.CQPrivateMessageEvent;
import net.lz1998.cq.robot.CQPlugin;
import net.lz1998.cq.robot.CoolQ;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class CQPluginMain extends CQPlugin implements EventHandler {
public CQPluginMain() {
// TODO(LamGC, 2020.04.21): SpringCQ无法适配MessageSenderBuilder
BotEventHandler.preLoad();
LoggerFactory.getLogger(this.toString())
.info("BotEventHandler.COMMAND_PREFIX = {}", BotEventHandler.COMMAND_PREFIX);
}
@Override
public int onPrivateMessage(CoolQ cq, CQPrivateMessageEvent event) {
//log.info("私聊消息到达: 发送者[{}], 消息内容: {}", event.getSender().getUserId(), event.getMessage());
return processMessage(cq, event);
}
@Override
public int onGroupMessage(CoolQ cq, CQGroupMessageEvent event) {
//log.info("群消息到达: 群[{}], 发送者[{}], 消息内容: {}", event.getGroupId(), event.getSender().getUserId(), event.getMessage());
return processMessage(cq, event);
}
@Override
public int onDiscussMessage(CoolQ cq, CQDiscussMessageEvent event) {
//log.info("讨论组消息到达: 群[{}], 发送者[{}], 消息内容: {}", event.getDiscussId(), event.getSender().getUserId(), event.getMessage());
return processMessage(cq, event);
}
public int processMessage(CoolQ cq, CQMessageEvent event) {
if(!BotEventHandler.match(event.getMessage())) {
return MESSAGE_IGNORE;
}
BotEventHandler.executor.executor(new SpringCQMessageEvent(cq, event));
return MESSAGE_BLOCK;
}
}

View File

@ -0,0 +1,64 @@
package net.lamgc.cgj.bot.framework.coolq.message;
import net.lamgc.cgj.bot.BotCode;
import net.lamgc.cgj.bot.event.MessageEvent;
import net.lamgc.cgj.bot.message.MessageSender;
import net.lamgc.cgj.bot.message.MessageSource;
import net.lz1998.cq.event.message.CQDiscussMessageEvent;
import net.lz1998.cq.event.message.CQGroupMessageEvent;
import net.lz1998.cq.event.message.CQMessageEvent;
import net.lz1998.cq.robot.CoolQ;
import java.net.InetSocketAddress;
import java.util.Objects;
public class SpringCQMessageEvent extends MessageEvent {
private final CoolQ cq;
private final MessageSender messageSender;
public SpringCQMessageEvent(CoolQ cq, CQMessageEvent messageEvent) {
super(messageEvent instanceof CQGroupMessageEvent ? (
(CQGroupMessageEvent) messageEvent).getGroupId() :
messageEvent instanceof CQDiscussMessageEvent ?
((CQDiscussMessageEvent) messageEvent).getDiscussId() : 0,
messageEvent.getUserId(), messageEvent.getMessage());
this.cq = Objects.requireNonNull(cq);
MessageSource source;
if(messageEvent instanceof CQGroupMessageEvent) {
source = MessageSource.Group;
} else if (messageEvent instanceof CQDiscussMessageEvent) {
source = MessageSource.Discuss;
} else {
source = MessageSource.Private;
}
messageSender = new SpringCQMessageSender(cq, source, source == MessageSource.Private ? getFromQQ() : getFromGroup());
}
@Override
public int sendMessage(final String message) {
return messageSender.sendMessage(message);
}
/**
* 通过CQ码获取图片下载链接.
* @param imageFileName 图片完整CQ码
* @return 图片下载链接
*/
@Override
public String getImageUrl(String imageFileName) {
BotCode code;
if(imageFileName.startsWith("[CQ:") && imageFileName.endsWith("]")) {
code = BotCode.parse(imageFileName);
return code.getParameter("url");
} else {
InetSocketAddress remoteAddress = cq.getBotSession().getRemoteAddress();
if(remoteAddress == null) {
throw new IllegalStateException("remoteAddress failed to get");
}
String file = cq.getImage(imageFileName).getData().getFile().replaceAll("\\\\", "/");
return "http://" + remoteAddress.getHostString() + ":5700/data" + file.substring(file.lastIndexOf("/data") + 5);
}
}
}

View File

@ -0,0 +1,32 @@
package net.lamgc.cgj.bot.framework.coolq.message;
import net.lamgc.cgj.bot.message.MessageSender;
import net.lamgc.cgj.bot.message.MessageSource;
import net.lz1998.cq.robot.CoolQ;
public class SpringCQMessageSender implements MessageSender {
private CoolQ coolQ;
private MessageSource source;
private long target;
public SpringCQMessageSender(CoolQ coolQ, MessageSource source, long target) {
this.coolQ = coolQ;
this.source = source;
this.target = target;
}
@Override
public int sendMessage(String message) {
switch (source) {
case Private:
return coolQ.sendPrivateMsg(target, message, false).getData().getMessageId();
case Group:
return coolQ.sendGroupMsg(target, message, false).getData().getMessageId();
case Discuss:
return coolQ.sendDiscussMsg(target, message, false).getData().getMessageId();
default:
return -1;
}
}
}

View File

@ -0,0 +1,20 @@
package net.lamgc.cgj.bot.framework.coolq.message;
import net.lamgc.cgj.bot.message.MessageSender;
import net.lamgc.cgj.bot.message.MessageSenderFactory;
import net.lamgc.cgj.bot.message.MessageSource;
import net.lz1998.cq.robot.CoolQ;
public class SpringCQMessageSenderFactory implements MessageSenderFactory {
private final CoolQ coolQ;
public SpringCQMessageSenderFactory(CoolQ coolQ) {
this.coolQ = coolQ;
}
@Override
public MessageSender createMessageSender(MessageSource source, long id) {
return new SpringCQMessageSender(coolQ, source, id);
}
}

View File

@ -0,0 +1,60 @@
package net.lamgc.cgj.bot.framework.mirai;
import net.lamgc.cgj.bot.event.BotEventHandler;
import net.lamgc.cgj.bot.framework.mirai.message.MiraiMessageEvent;
import net.lamgc.cgj.bot.message.MessageSenderBuilder;
import net.lamgc.cgj.bot.framework.mirai.message.MiraiMessageSenderFactory;
import net.mamoe.mirai.Bot;
import net.mamoe.mirai.BotFactoryJvm;
import net.mamoe.mirai.japt.Events;
import net.mamoe.mirai.message.FriendMessage;
import net.mamoe.mirai.message.GroupMessage;
import net.mamoe.mirai.utils.BotConfiguration;
import org.apache.commons.net.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.Properties;
public class MiraiMain implements Closeable {
private final Logger log = LoggerFactory.getLogger(this.toString());
private Bot bot;
private final static Properties botProperties = new Properties();
public void init() {
Runtime.getRuntime().addShutdownHook(new Thread(this::close));
try {
Class.forName(BotEventHandler.class.getName());
} catch (ClassNotFoundException e) {
log.error("加载BotEventHandler时发生异常", e);
return;
}
File botPropFile = new File("./bot.properties");
try (Reader reader = new BufferedReader(new FileReader(botPropFile))) {
botProperties.load(reader);
} catch (IOException e) {
log.error("机器人配置文件读取失败!", e);
return;
}
bot = BotFactoryJvm.newBot(Long.parseLong(botProperties.getProperty("bot.qq", "0")), Base64.decodeBase64(botProperties.getProperty("bot.password", "")), new BotConfiguration());
Events.subscribeAlways(GroupMessage.class, (msg) -> BotEventHandler.executor.executor(new MiraiMessageEvent(msg)));
Events.subscribeAlways(FriendMessage.class, (msg) -> BotEventHandler.executor.executor(new MiraiMessageEvent(msg)));
bot.login();
MessageSenderBuilder.setCurrentMessageSenderFactory(new MiraiMessageSenderFactory(bot));
BotEventHandler.preLoad();
bot.join();
}
public void close() {
log.warn("正在关闭机器人...");
bot.close(null);
log.warn("机器人已关闭.");
}
}

View File

@ -0,0 +1,39 @@
package net.lamgc.cgj.bot.framework.mirai.message;
import net.lamgc.cgj.bot.event.MessageEvent;
import net.lamgc.cgj.bot.message.MessageSender;
import net.lamgc.cgj.bot.message.MessageSource;
import net.mamoe.mirai.message.ContactMessage;
import net.mamoe.mirai.message.GroupMessage;
import net.mamoe.mirai.message.data.MessageUtils;
import java.util.Objects;
public class MiraiMessageEvent extends MessageEvent {
private final ContactMessage messageObject;
private final MessageSender messageSender;
public MiraiMessageEvent(ContactMessage message) {
super(message instanceof GroupMessage ? ((GroupMessage) message).getGroup().getId() : 0,
message.getSender().getId(), message.getMessage().contentToString());
this.messageObject = Objects.requireNonNull(message);
if(message instanceof GroupMessage) {
messageSender = new MiraiMessageSender(((GroupMessage) message).getGroup(), MessageSource.Group);
} else {
messageSender = new MiraiMessageSender(message.getSender(), MessageSource.Private);
}
}
@Override
public int sendMessage(final String message) {
return messageSender.sendMessage(message);
}
@Override
public String getImageUrl(String imageId) {
return messageObject.getBot().queryImageUrl(MessageUtils.newImage(imageId));
}
}

View File

@ -0,0 +1,183 @@
package net.lamgc.cgj.bot.framework.mirai.message;
import com.google.common.base.Strings;
import net.lamgc.cgj.bot.BotCode;
import net.lamgc.cgj.bot.cache.CacheStore;
import net.lamgc.cgj.bot.cache.HotDataCacheStore;
import net.lamgc.cgj.bot.cache.LocalHashCacheStore;
import net.lamgc.cgj.bot.cache.StringRedisCacheStore;
import net.lamgc.cgj.bot.event.BotEventHandler;
import net.lamgc.cgj.bot.message.MessageSender;
import net.lamgc.cgj.bot.message.MessageSource;
import net.mamoe.mirai.Bot;
import net.mamoe.mirai.contact.Contact;
import net.mamoe.mirai.message.data.Image;
import net.mamoe.mirai.message.data.Message;
import net.mamoe.mirai.message.data.MessageChain;
import net.mamoe.mirai.message.data.MessageUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MiraiMessageSender implements MessageSender {
private final Contact member;
private final MessageSource source;
private final static Logger log = LoggerFactory.getLogger("MiraiMessageSender");
private final static CacheStore<String> imageIdCache = new HotDataCacheStore<>(
new StringRedisCacheStore(BotEventHandler.redisServer, "mirai.imageId"),
new LocalHashCacheStore<>(),
5400000, 1800000);
/**
* 使用id构造发送器
* @param bot 机器人对象
* @param source 消息源类型
* @param id id, 将会根据消息源类型判断为什么号(QQ号或群号)
* @throws NoSuchElementException 当在机器人好友列表或群列表里没有这个好友或群的时候抛出
*/
public MiraiMessageSender(Bot bot, MessageSource source, long id) {
this(source == MessageSource.Private ? bot.getFriend(id) : bot.getGroup(id), source);
}
/**
* 通过联系人对象构造发送器
* @param contact 联系人
* @param source 消息源类型
*/
public MiraiMessageSender(Contact contact, MessageSource source) {
this.member = contact;
this.source = source;
}
@Override
public int sendMessage(final String message) {
log.debug("处理前的消息内容:\n{}", message);
Message msgBody = processMessage(Objects.requireNonNull(message));
log.debug("处理后的消息内容(可能出现乱序的情况, 但实际上顺序是没问题的):\n{}", msgBody);
member.sendMessage(msgBody);
return 0;
}
private final static Pattern cqCodePattern = BotCode.getCodePattern();
private Message processMessage(final String message) {
Matcher matcher = cqCodePattern.matcher(message);
ArrayList<String> cqCode = new ArrayList<>();
while (matcher.find()) {
cqCode.add(matcher.group());
}
String[] texts = message
.replaceAll("&", "&38")
.replaceAll("\\{", "&" + Character.getNumericValue('{'))
.replaceAll(cqCodePattern.pattern(), "|{BotCode}|")
.replaceAll("&" + Character.getNumericValue('{'), "{")
.replaceAll("&38", "&")
.split("\\|");
MessageChain messages = MessageUtils.newChain().plus("");
int codeIndex = 0;
for(String text : texts) {
if(text.equals("{BotCode}")) {
BotCode code;
try {
code = BotCode.parse(cqCode.get(codeIndex++));
} catch(IllegalArgumentException e) {
log.warn("解析待发送消息内的BotCode时发生异常, 请检查错误格式BotCode的来源并尽快排错!", e);
continue;
}
messages = messages.plus(processBotCode(code));
} else {
messages = messages.plus(text);
}
}
return messages;
}
private Message processBotCode(BotCode code) {
switch(code.getFunctionName().toLowerCase()) {
case "image":
Image img;
if(code.containsParameter("id")) {
img = MessageUtils.newImage(code.getParameter("id"));
} else if(code.containsParameter("absolutePath")) {
img = uploadImage(code);
} else {
return MessageUtils.newChain("(参数不存在)");
}
if(code.getParameter("flashImage").equalsIgnoreCase("true")) {
return MessageUtils.flash(img);
} else {
return img;
}
default:
log.warn("解析到不支持的BotCode: {}", code);
return MessageUtils.newChain("(不支持的BotCode)");
}
}
/**
* 存在缓存的上传图片.
* @param code 图片BotCode
* @return Image对象
*/
public Image uploadImage(BotCode code) {
log.debug("传入BotCode信息:\n{}", code);
String absolutePath = code.getParameter("absolutePath");
if(Strings.isNullOrEmpty(absolutePath)) {
throw new IllegalArgumentException("BotCode does not contain the absolutePath parameter");
}
String imageName = code.getParameter("imageName");
if(!Strings.isNullOrEmpty(imageName)) {
Image image = null;
imageName = (source + "." + imageName).intern();
if(!imageIdCache.exists(imageName) ||
Strings.nullToEmpty(code.getParameter("updateCache")).equalsIgnoreCase("true")) {
synchronized (imageName) {
if(!imageIdCache.exists(imageName) ||
Strings.nullToEmpty(code.getParameter("updateCache")) .equalsIgnoreCase("true")) {
log.debug("imageName [{}] 缓存失效或强制更新, 正在更新缓存...", imageName);
image = uploadImage0(new File(absolutePath));
String cacheExpireAt;
long expireTime = 864000000; // 10d
if(!Strings.isNullOrEmpty(cacheExpireAt = code.getParameter("cacheExpireAt"))) {
try {
expireTime = Integer.parseInt(cacheExpireAt);
} catch (NumberFormatException e) {
log.warn("BotCode中的cacheExpireAt参数无效: {}", cacheExpireAt);
}
}
imageIdCache.update(imageName, image.getImageId(), expireTime);
log.info("imageName [{}] 缓存更新完成.(有效时间: {})", imageName, expireTime);
} else {
log.debug("ImageName: [{}] 缓存命中.", imageName);
}
}
} else {
log.debug("ImageName: [{}] 缓存命中.", imageName);
}
if(image == null) {
image = MessageUtils.newImage(imageIdCache.getCache(imageName));
}
log.debug("ImageName: {}, ImageId: {}", imageName, image.getImageId());
return image;
} else {
log.debug("未设置imageName, 无法使用缓存.");
return uploadImage0(new File(absolutePath));
}
}
private Image uploadImage0(File imageFile) {
return member.uploadImage(imageFile);
}
}

View File

@ -0,0 +1,28 @@
package net.lamgc.cgj.bot.framework.mirai.message;
import net.lamgc.cgj.bot.message.MessageSender;
import net.lamgc.cgj.bot.message.MessageSenderFactory;
import net.lamgc.cgj.bot.message.MessageSource;
import net.mamoe.mirai.Bot;
public class MiraiMessageSenderFactory implements MessageSenderFactory {
private final Bot bot;
public MiraiMessageSenderFactory(Bot bot) {
this.bot = bot;
}
@Override
public MessageSender createMessageSender(MessageSource source, long id) throws Exception {
switch(source) {
case Group:
case Discuss:
return new MiraiMessageSender(bot.getGroup(id), source);
case Private:
return new MiraiMessageSender(bot.getFriend(id), source);
default:
throw new NoSuchFieldException(source.toString());
}
}
}