diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.md b/.github/ISSUE_TEMPLATE/Bug_Report.md index 939591d..ac9f6db 100644 --- a/.github/ISSUE_TEMPLATE/Bug_Report.md +++ b/.github/ISSUE_TEMPLATE/Bug_Report.md @@ -1,26 +1,35 @@ --- -name: Bug Report -about: Use this template to feedback bugs. +name: Bug 反馈 +about: 使用这个模板反馈应用问题。 title: '' labels: bug assignees: '' --- -## Environmental information ## -OS(e.g: Windows 10 1909): -Java(e.g: Oracle Jdk 8.242): -Issue version(versionTag or commitId): +## 环境信息 ## +系统(例如: Windows 10 1909): `` +Java版本(例如: Oracle Jdk 8.242): `` + + +发生问题所在的版本: `` -## Problem description ## -// Describe the problem in as much detail as possible here +## 问题描述 ## + -## Expected behavior ## -// What will this function do under normal circumstances? +## 预期行为 ## + -## Actual behavior ## -// But what does this feature actually look like? +## 实际行为 ## + -## Recurrence steps ## -// What can we do to recreate this situation? +## 复现步骤 ## + 1. + +## 相关信息 ## +### 日志 ### + +``` + +``` diff --git a/.github/ISSUE_TEMPLATE/Feature_Report.md b/.github/ISSUE_TEMPLATE/Feature_Report.md new file mode 100644 index 0000000..9bc11b3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_Report.md @@ -0,0 +1,11 @@ +--- +name: 功能/想法 提议 +about: 使用这个模板将你对应用的想法提出来,或许我们会采纳! +title: '' +labels: function, question +assignees: '' + +--- + + + diff --git a/Dockerfile.sample b/Dockerfile.sample index cacbf9a..fb1aa62 100644 --- a/Dockerfile.sample +++ b/Dockerfile.sample @@ -5,6 +5,7 @@ ENV jarFileName=ContentGrabbingJi-exec.jar ENV CGJ_REDIS_URI="127.0.0.1:6379" ENV CGJ_PROXY="" RUN mkdir /data/ -CMD [java, "-Dcgj.logsPath=/data/logs", "-jar", "/CGJ.jar", "botMode", "-botDataDir=/data"] +ENTRYPOINT ["/usr/java/openjdk-14/bin/java", "-Duser.timezone=GMT+8"] +CMD ["-Dcgj.logsPath=/data/logs", "-jar", "/CGJ.jar", "botMode", "-botDataDir=/data"] COPY ${jarFileName} /CGJ.jar diff --git a/README.md b/README.md index ef5b8ee..049c69e 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,14 @@ 你应该注意到了,在部署过程中,你需要设置一个管理员QQ的配置,色图姬支持通过命令来管理色图姬的运行。 目前支持的管理员命令: ```bash -# 清除缓存 +# 清除缓存(慎用) +# 该操作将会清除Redis服务器内的所有数据, 以及色图姬下载到本地的所有图片缓存. .cgjadmin cleanCache # 设置配置项 # 如果不使用group参数, 则设置全局配置 +# 注意: 配置项设置后需要使用`.cgjadmin saveProperties`才会保存到文件中, +# 如不保存, 则仅能在本次运行中生效(或使用`.cgjadmin loadProperties`重新加载后失效). .cgjadmin setProperty <-key 配置项名> <-value 配置项新值> [-group 指定群组] # 查询配置项 @@ -79,7 +82,8 @@ # 增加群组作品推送 # 如果增加了original参数, 则图片为原图发送 # 如果不指定group参数, 则群组为命令发送所在群组 -.cgjadmin addPushGroup [-group 指定群组号] [-minTime 最小] [-floatTime 随机时间范围] [-rankingStart 排行榜起始排名] +# 最长发送时间 = 最短发送时间 + 随机时间范围 +.cgjadmin addPushGroup [-group 指定群组号] [-minTime 最短发送时间] [-floatTime 随机时间范围] [-rankingStart 排行榜起始排名] [-rankingStop 排行榜结束排名] [-mode 排行榜模式] [-type 排行榜类型] [-original] # 删除群组推送功能 diff --git a/documents/interfaces/Pixiv搜索推荐候选接口.md b/documents/interfaces/Pixiv搜索推荐候选接口.md index f59bc6f..e043215 100644 --- a/documents/interfaces/Pixiv搜索推荐候选接口.md +++ b/documents/interfaces/Pixiv搜索推荐候选接口.md @@ -7,9 +7,11 @@ GET https://www.pixiv.net/rpc/cps.php? ``` -- 是否需要登录: `是` +- 是否需要登录: `否` - 是否为Pixiv标准接口返回格式: `否` -- 是否需要Referer请求头: `否` +- 是否需要Referer请求头: `是` + +> 补充: Referer请求头只要是Pixiv的就可以了. ### 参数 ### Url参数: diff --git a/pom.xml b/pom.xml index efc69c6..cfbdcf2 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.lamgc ContentGrabbingJi - 2.5.2-20200630.2-SNAPSHOT + 2.5.2-20200709.1-SNAPSHOT diff --git a/src/main/java/net/lamgc/cgj/bot/RandomIntervalSendTimer.java b/src/main/java/net/lamgc/cgj/bot/RandomIntervalSendTimer.java index ce693ab..229e426 100644 --- a/src/main/java/net/lamgc/cgj/bot/RandomIntervalSendTimer.java +++ b/src/main/java/net/lamgc/cgj/bot/RandomIntervalSendTimer.java @@ -4,7 +4,6 @@ import com.google.common.base.Throwables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.reflect.Field; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -23,7 +22,7 @@ public class RandomIntervalSendTimer extends TimerTask { private final long time; private final int floatTime; private final AtomicBoolean loop = new AtomicBoolean(); - private final AtomicBoolean start = new AtomicBoolean(); + private final AtomicBoolean running = new AtomicBoolean(); private final String hashId = Integer.toHexString(this.hashCode()); @@ -88,7 +87,6 @@ public class RandomIntervalSendTimer extends TimerTask { this.sender = sender; this.time = time; this.floatTime = floatTime; - timerMap.put(timerId, this); if(startNow) { start(loop); } @@ -108,21 +106,18 @@ public class RandomIntervalSendTimer extends TimerTask { Date nextDate = new Date(); nextDate.setTime(nextDate.getTime() + nextDelay); log.info("定时器 {} 下一延迟: {}ms ({})", hashId, nextDelay, nextDate); - if(start.get()) { - try { - Field state = this.getClass().getSuperclass().getDeclaredField("state"); - state.setAccessible(true); - state.setInt(this, 0); - state.setAccessible(false); - } catch (NoSuchFieldException | IllegalAccessException e) { - e.printStackTrace(); - return; - } + if(running.get()) { + reset(); + return; } - start.set(true); + running.set(true); timer.schedule(this, nextDelay); } + public void reset() { + timerMap.put(timerId, (RandomIntervalSendTimer) clone()); + } + @Override public void run() { log.info("定时器 {} 开始执行...(Sender: {}@{})", this.hashId, sender.getClass().getSimpleName(), sender.hashCode()); @@ -145,7 +140,7 @@ public class RandomIntervalSendTimer extends TimerTask { */ @Override public boolean cancel() { - start.set(false); + running.set(false); loop.set(false); return super.cancel(); } @@ -158,4 +153,18 @@ public class RandomIntervalSendTimer extends TimerTask { timerMap.remove(this.timerId); } + /** + * 克隆一个参数完全一样的TimerTask对象. + * @return 返回对象不同, 参数相同的TimerTask对象. + */ + @Override + @SuppressWarnings("MethodDoesntCallSuperMethod") + public Object clone() { + RandomIntervalSendTimer newTimerTask = new RandomIntervalSendTimer( + this.timerId, this.sender, + time, floatTime, + running.get(), loop.get()); + this.destroy(); + return newTimerTask; + } } diff --git a/src/main/java/net/lamgc/cgj/bot/event/BotEventHandler.java b/src/main/java/net/lamgc/cgj/bot/event/BotEventHandler.java index 8975042..cfca657 100644 --- a/src/main/java/net/lamgc/cgj/bot/event/BotEventHandler.java +++ b/src/main/java/net/lamgc/cgj/bot/event/BotEventHandler.java @@ -26,7 +26,6 @@ import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -40,15 +39,13 @@ public class BotEventHandler implements EventHandler { private final static Logger log = LoggerFactory.getLogger(BotEventHandler.class); - private final static Map muteStateMap = new Hashtable<>(); - /** * 消息事件执行器 */ private final static EventExecutor executor = new EventExecutor(new TimeLimitThreadPoolExecutor( - 180000, // 3minThr - Math.max(Runtime.getRuntime().availableProcessors(), 4), - Math.min(Math.max(Runtime.getRuntime().availableProcessors(), 4), 32), + 180000, // 3min limit + Math.max(Runtime.getRuntime().availableProcessors(), 4), // 4 ~ processors + Math.min(Math.max(Runtime.getRuntime().availableProcessors() * 2, 8), 32),// (8 ~ processors * 2) ~ 32 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1536), @@ -167,9 +164,6 @@ public class BotEventHandler implements EventHandler { log.debug(event.toString()); if(mismatch(msg)) { return; - } else if(isMute(event.getFromGroup())) { - log.debug("机器人已被禁言, 忽略请求."); - return; } Pattern pattern = Pattern.compile("/\\s*(\".+?\"|[^:\\s])+((\\s*:\\s*(\".+?\"|[^\\s])+)|)|(\".+?\"|[^\"\\s])+"); @@ -236,7 +230,7 @@ public class BotEventHandler implements EventHandler { } } long processTime = System.currentTimeMillis() - time; - if(!Objects.isNull(result) && result instanceof String && !isMute(event.getFromGroup())) { + if(!Objects.isNull(result) && result instanceof String) { try { int sendResult = event.sendMessage((String) result); if (sendResult < 0) { @@ -248,8 +242,6 @@ public class BotEventHandler implements EventHandler { } catch (Exception e) { log.error("发送消息时发生异常", e); } - } else if(isMute(event.getFromGroup())) { - log.warn("命令反馈时机器人已被禁言, 跳过反馈."); } long totalTime = System.currentTimeMillis() - time; log.info("命令反馈完成.(事件耗时: {}ms, P: {}%({}ms), R: {}%({}ms))", totalTime, @@ -266,45 +258,4 @@ public class BotEventHandler implements EventHandler { return !message.startsWith(COMMAND_PREFIX) && !message.startsWith(ADMIN_COMMAND_PREFIX); } - /** - * 查询某群组中Bot是否被禁言 - * @param groupId 待查询的群组号 - * @return 如果被禁言, 返回true, 如果未被禁言或禁言情况未知, 返回false - */ - private static boolean isMute(long groupId) { - Boolean mute = isMute(groupId, false); - return mute != null && mute; - } - - /** - * 查询某群是否被禁言. - * @param groupId 群组Id - * @param rawValue 是否返回原始值(当没有该群状态, 且本参数为true时, 将返回null) - * @return 返回状态值, 如无该群禁言记录且rawValue = true, 则返回null - */ - public static Boolean isMute(long groupId, boolean rawValue) { - if(groupId <= 0) { - return false; - } - AtomicBoolean state = muteStateMap.get(groupId); - if(state == null && rawValue) { - return null; - } - return state != null && state.get(); - } - - /** - * 设置机器人禁言状态. - *

设置该项可防止因机器人在禁言期间反馈请求导致被封号.

- * @param mute 如果被禁言, 传入true - */ - public static void setMuteState(long groupId, boolean mute) { - if(!muteStateMap.containsKey(groupId)) { - muteStateMap.put(groupId, new AtomicBoolean(mute)); - } else { - muteStateMap.get(groupId).set(mute); - } - log.warn("群组 {} 机器人禁言状态已变更: {}", groupId, mute ? "已禁言" : "已解除"); - } - } diff --git a/src/main/java/net/lamgc/cgj/bot/event/MessageEvent.java b/src/main/java/net/lamgc/cgj/bot/event/MessageEvent.java index f27cdac..0abb79b 100644 --- a/src/main/java/net/lamgc/cgj/bot/event/MessageEvent.java +++ b/src/main/java/net/lamgc/cgj/bot/event/MessageEvent.java @@ -57,7 +57,7 @@ public abstract class MessageEvent implements EventObject, MessageSender { @Override public String toString() { - return this.getClass().getSimpleName() + "@" + this.hashCode() + "{" + + return this.getClass().getSimpleName() + "@" + Integer.toHexString(this.hashCode()) + "{" + "fromGroup=" + getFromGroup() + ", fromQQ=" + getFromQQ() + ", message='" + getMessage() + '\'' + diff --git a/src/main/java/net/lamgc/cgj/bot/framework/coolq/CQPluginMain.java b/src/main/java/net/lamgc/cgj/bot/framework/coolq/CQPluginMain.java index d9122cd..28c43b1 100644 --- a/src/main/java/net/lamgc/cgj/bot/framework/coolq/CQPluginMain.java +++ b/src/main/java/net/lamgc/cgj/bot/framework/coolq/CQPluginMain.java @@ -17,12 +17,12 @@ import org.springframework.stereotype.Component; import java.util.concurrent.atomic.AtomicBoolean; @Component +@SuppressWarnings("unused") public class CQPluginMain extends CQPlugin implements EventHandler { private final static AtomicBoolean initialState = new AtomicBoolean(); public CQPluginMain() { - LoggerFactory.getLogger(CQPluginMain.class) .info("BotEventHandler.COMMAND_PREFIX = {}", BotEventHandler.COMMAND_PREFIX); } diff --git a/src/main/java/net/lamgc/cgj/bot/framework/mirai/MiraiMain.java b/src/main/java/net/lamgc/cgj/bot/framework/mirai/MiraiMain.java index 8cb8bd1..a5f1833 100644 --- a/src/main/java/net/lamgc/cgj/bot/framework/mirai/MiraiMain.java +++ b/src/main/java/net/lamgc/cgj/bot/framework/mirai/MiraiMain.java @@ -8,6 +8,7 @@ import net.lamgc.cgj.bot.framework.FrameworkResources; import net.lamgc.cgj.bot.framework.mirai.message.MiraiMessageEvent; import net.lamgc.cgj.bot.framework.mirai.message.MiraiMessageSenderFactory; import net.lamgc.cgj.bot.message.MessageSenderBuilder; +import net.lamgc.cgj.bot.util.GroupMuteManager; import net.mamoe.mirai.Bot; import net.mamoe.mirai.BotFactoryJvm; import net.mamoe.mirai.event.events.BotMuteEvent; @@ -32,7 +33,9 @@ public class MiraiMain implements Framework { private Bot bot; - private final static Properties botProperties = new Properties(); + private final Properties botProperties = new Properties(); + + private final GroupMuteManager muteManager = new GroupMuteManager(); @Override public void init(FrameworkResources resources) { @@ -77,9 +80,9 @@ public class MiraiMain implements Framework { Events.subscribeAlways(FriendMessageEvent.class, this::executeMessageEvent); Events.subscribeAlways(TempMessageEvent.class, this::executeMessageEvent); Events.subscribeAlways(BotMuteEvent.class, - event -> BotEventHandler.setMuteState(event.getGroup().getId(), true)); + event -> muteManager.setMuteState(event.getGroup().getId(), true)); Events.subscribeAlways(BotUnmuteEvent.class, - event -> BotEventHandler.setMuteState(event.getGroup().getId(), false)); + event -> muteManager.setMuteState(event.getGroup().getId(), false)); bot.login(); MessageSenderBuilder.setCurrentMessageSenderFactory(new MiraiMessageSenderFactory(bot)); ApplicationBoot.initialBot(); @@ -100,9 +103,12 @@ public class MiraiMain implements Framework { log.debug("Mirai Message: {}", message); if(message instanceof GroupMessageEvent) { GroupMessageEvent GroupMessageEvent = (GroupMessageEvent) message; - if(BotEventHandler.isMute(GroupMessageEvent.getGroup().getId(), true) == null) { - BotEventHandler.setMuteState(GroupMessageEvent.getGroup().getId(), + Boolean muteState = muteManager.isMute(GroupMessageEvent.getGroup().getId(), true); + if(muteState == null) { + muteManager.setMuteState(GroupMessageEvent.getGroup().getId(), ((GroupMessageEvent) message).getGroup().getBotMuteRemaining() != 0); + } else if(muteState) { + return; } } BotEventHandler.executeMessageEvent(MiraiMessageEvent.covertEventObject(message)); diff --git a/src/main/java/net/lamgc/cgj/bot/util/GroupMuteManager.java b/src/main/java/net/lamgc/cgj/bot/util/GroupMuteManager.java new file mode 100644 index 0000000..6193839 --- /dev/null +++ b/src/main/java/net/lamgc/cgj/bot/util/GroupMuteManager.java @@ -0,0 +1,48 @@ +package net.lamgc.cgj.bot.util; + +import java.util.Hashtable; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * 群禁言管理器. + *

该管理器用于存取群组禁言状态.

+ */ +public class GroupMuteManager { + + private final Map muteStateMap = new Hashtable<>(); + + /** + * 查询某群是否被禁言. + * @param groupId 群组Id + * @param rawValue 是否返回原始值(当没有该群状态, 且本参数为true时, 将返回null) + * @return 返回状态值, 如无该群禁言记录且rawValue = true, 则返回null + */ + public Boolean isMute(long groupId, boolean rawValue) { + if(groupId <= 0) { + return false; + } + AtomicBoolean state = muteStateMap.get(groupId); + if(state == null && rawValue) { + return null; + } + return state != null && state.get(); + } + + /** + * 设置机器人禁言状态. + *

设置该项可防止因机器人在禁言期间反馈请求导致被封号.

+ * @param mute 如果被禁言, 传入true + */ + public void setMuteState(long groupId, boolean mute) { + if(groupId <= 0) { + return; + } + if(!muteStateMap.containsKey(groupId)) { + muteStateMap.put(groupId, new AtomicBoolean(mute)); + } else { + muteStateMap.get(groupId).set(mute); + } + } + +} diff --git a/src/main/java/net/lamgc/cgj/pixiv/PixivDownload.java b/src/main/java/net/lamgc/cgj/pixiv/PixivDownload.java index 7c0f6df..a113c87 100644 --- a/src/main/java/net/lamgc/cgj/pixiv/PixivDownload.java +++ b/src/main/java/net/lamgc/cgj/pixiv/PixivDownload.java @@ -402,7 +402,7 @@ public class PixivDownload { if(resultObject.get("error").getAsBoolean()) { String message = resultObject.get("message").getAsString(); log.warn("作品页面接口请求错误, 错误信息: {}", message); - throw new HttpRequestException(response); + throw new HttpRequestException(response.getStatusLine(), resultObject.toString()); } JsonArray linkArray = resultObject.getAsJsonArray("body"); diff --git a/src/main/java/net/lamgc/cgj/pixiv/PixivUgoiraBuilder.java b/src/main/java/net/lamgc/cgj/pixiv/PixivUgoiraBuilder.java index 5bc321e..73efdb7 100644 --- a/src/main/java/net/lamgc/cgj/pixiv/PixivUgoiraBuilder.java +++ b/src/main/java/net/lamgc/cgj/pixiv/PixivUgoiraBuilder.java @@ -1,5 +1,6 @@ package net.lamgc.cgj.pixiv; +import com.google.common.io.ByteStreams; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -12,7 +13,6 @@ import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.util.EntityUtils; -import org.apache.tomcat.util.http.fileupload.util.Streams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -127,14 +127,13 @@ public final class PixivUgoiraBuilder { HashMap frameMap = new HashMap<>(frames.size()); while((entry = zipInputStream.getNextEntry()) != null) { log.trace("ZipEntry {} 正在接收...", entry); - Streams.copy(zipInputStream, cacheOutputStream, false); + ByteStreams.copy(zipInputStream, cacheOutputStream); frameMap.put(entry.getName(), new ByteArrayInputStream(cacheOutputStream.toByteArray())); log.trace("ZipEntry {} 已接收完成.", entry); cacheOutputStream.reset(); } - - InputStream firstFrameInput = frameMap.get("000000.jpg"); + InputStream firstFrameInput = frameMap.get(frames.get(0).getAsJsonObject().get("file").getAsString()); BufferedImage firstFrame = ImageIO.read(firstFrameInput); firstFrameInput.reset(); if(width != firstFrame.getWidth() || height != firstFrame.getHeight()) { diff --git a/src/test/java/net/lamgc/cgj/bot/util/GroupMuteManagerTest.java b/src/test/java/net/lamgc/cgj/bot/util/GroupMuteManagerTest.java new file mode 100644 index 0000000..3a04325 --- /dev/null +++ b/src/test/java/net/lamgc/cgj/bot/util/GroupMuteManagerTest.java @@ -0,0 +1,30 @@ +package net.lamgc.cgj.bot.util; + +import org.junit.Assert; +import org.junit.Test; + +public class GroupMuteManagerTest { + + @Test + public void muteStateTest() { + GroupMuteManager manager = new GroupMuteManager(); + Assert.assertNull(manager.isMute(1, true)); // 未设置的群组返回null + Assert.assertFalse(manager.isMute(1, false)); // 未设置就返回false + manager.setMuteState(1, true); // mute == true + Assert.assertNotNull(manager.isMute(1, true)); // 第一次设置后不为null + Assert.assertTrue(manager.isMute(1, false)); // 确保条件正常 + manager.setMuteState(2, true); // 不能出现不同群号的冲突 + manager.setMuteState(1, false); + Assert.assertTrue(manager.isMute(2, false)); + Assert.assertNotNull(manager.isMute(1, true)); // 变更为false后依然不能返回null + Assert.assertFalse(manager.isMute(1, false)); + } + + @Test + public void invalidGroupIdTest() { + GroupMuteManager manager = new GroupMuteManager(); + manager.setMuteState(-1, true); // 设置应该是无效的 + Assert.assertFalse(manager.isMute(-1, false)); // 由于设置无效, 返回false即可 + } + +}