mirror of
https://github.com/LamGC/ContentGrabbingJi.git
synced 2025-07-01 21:07:25 +00:00
Compare commits
22 Commits
v2.3.0
...
v2.5.0-202
Author | SHA1 | Date | |
---|---|---|---|
c8fe2c3fdd | |||
49a33d4078 | |||
cd1d2316ee | |||
cf08353ed9 | |||
bef6a684b9 | |||
04960889b4 | |||
0dc8fc78b4 | |||
40c5284be2 | |||
46cb47c7d1 | |||
fe213deecb | |||
f279d99fda | |||
a09aef5be2 | |||
766411fa09 | |||
84b544cea9 | |||
2f492f5b03 | |||
15af939c3f | |||
a28cb142b4 | |||
e8fda3214b | |||
fba6d3532c | |||
0075446412 | |||
2388cd419e | |||
7f83b16118 |
10
Dockerfile.sample
Normal file
10
Dockerfile.sample
Normal file
@ -0,0 +1,10 @@
|
||||
FROM openjdk:8u252-jre
|
||||
|
||||
ENV jarFileName=ContentGrabbingJi-exec.jar
|
||||
ENV CGJ_REDIS_URI="127.0.0.1:6379"
|
||||
ENV CGJ_PROXY=""
|
||||
RUN mkdir /data/
|
||||
|
||||
COPY ${jarFileName} /CGJ.jar
|
||||
|
||||
CMD ["java", "-Dcgj.logsPath=/data/logs", "-jar", "/CGJ.jar", "botMode", "-botDataDir=/data"]
|
165
LICENSE
Normal file
165
LICENSE
Normal file
@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
6
pom.xml
6
pom.xml
@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>net.lamgc</groupId>
|
||||
<artifactId>ContentGrabbingJi</artifactId>
|
||||
<version>2.3.0</version>
|
||||
<version>2.5.0-20200505.1-SNAPSHOT</version>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
@ -86,7 +86,7 @@
|
||||
<dependency>
|
||||
<groupId>net.lamgc</groupId>
|
||||
<artifactId>java-utils</artifactId>
|
||||
<version>1.1.0_5-SNAPSHOT</version>
|
||||
<version>1.2.0_20200505.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@ -172,7 +172,7 @@
|
||||
<dependency>
|
||||
<groupId>net.lamgc</groupId>
|
||||
<artifactId>PixivLoginProxyServer</artifactId>
|
||||
<version>1.1.0</version>
|
||||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup</groupId>
|
||||
|
@ -9,8 +9,9 @@ import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import net.lamgc.cgj.bot.framework.coolq.CQConfig;
|
||||
import net.lamgc.cgj.bot.framework.mirai.MiraiMain;
|
||||
import net.lamgc.cgj.pixiv.*;
|
||||
import net.lamgc.cgj.proxy.PixivAccessProxyServer;
|
||||
import net.lamgc.cgj.pixiv.PixivDownload;
|
||||
import net.lamgc.cgj.pixiv.PixivSearchBuilder;
|
||||
import net.lamgc.cgj.pixiv.PixivURL;
|
||||
import net.lamgc.plps.PixivLoginProxyServer;
|
||||
import net.lamgc.utils.base.ArgumentsProperties;
|
||||
import net.lamgc.utils.base.runner.Argument;
|
||||
@ -20,9 +21,7 @@ import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.CookieStore;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -52,9 +51,19 @@ public class Main {
|
||||
public static HttpHost proxy;
|
||||
|
||||
public static void main(String[] args) throws IOException, ClassNotFoundException {
|
||||
log.trace("ContentGrabbingJi 正在启动...");
|
||||
log.debug("Args: {}, LogsPath: {}", Arrays.toString(args), System.getProperty("cgj.logsPath"));
|
||||
log.debug("运行目录: {}", System.getProperty("user.dir"));
|
||||
ArgumentsProperties argsProp = new ArgumentsProperties(args);
|
||||
if(argsProp.containsKey("proxy")) {
|
||||
URL proxyUrl = new URL(argsProp.getValue("proxy"));
|
||||
|
||||
|
||||
if(!getSettingToSysProp(argsProp, "proxy", null)) {
|
||||
getEnvSettingToSysProp("CGJ_PROXY", "proxy", null);
|
||||
}
|
||||
|
||||
String proxyAddress = System.getProperty("cgj.proxy");
|
||||
if(!Strings.isNullOrEmpty(proxyAddress)) {
|
||||
URL proxyUrl = new URL(proxyAddress);
|
||||
proxy = new HttpHost(proxyUrl.getHost(), proxyUrl.getPort());
|
||||
log.info("已启用Http协议代理:{}", proxy.toHostString());
|
||||
} else {
|
||||
@ -65,28 +74,20 @@ public class Main {
|
||||
log.error("创建文件夹失败!");
|
||||
}
|
||||
|
||||
// TODO: 需要修改参数名了, 大概改成类似于 workerDir这样的吧
|
||||
if(argsProp.containsKey("cqRootDir")) {
|
||||
log.info("cqRootDir: {}", argsProp.getValue("cqRootDir"));
|
||||
System.setProperty("cgj.cqRootDir", argsProp.getValue("cqRootDir"));
|
||||
} else {
|
||||
log.warn("未设置cqRootDir, 当前运行目录将作为酷Q机器人所在目录.");
|
||||
System.setProperty("cgj.cqRootDir", "./");
|
||||
if(!getSettingToSysProp(argsProp, "botDataDir", "./") &&
|
||||
!getEnvSettingToSysProp("CGJ_BOT_DATA_DIR", "botDataDir", "./")) {
|
||||
log.warn("未设置botDataDir, 当前运行目录将作为酷Q机器人所在目录.");
|
||||
}
|
||||
if(!getSettingToSysProp(argsProp, "redisAddress", "127.0.0.1") &&
|
||||
!getEnvSettingToSysProp("CGJ_REDIS_URI", "redisAddress", "127.0.0.1")) {
|
||||
log.warn("未设置RedisAddress, 将使用默认值连接Redis服务器(127.0.0.1:6379)");
|
||||
}
|
||||
|
||||
if(argsProp.containsKey("redisAddr")) {
|
||||
log.info("redisAddress: {}", argsProp.getValue("redisAddr"));
|
||||
System.setProperty("cgj.redisAddress", argsProp.getValue("redisAddr"));
|
||||
} else {
|
||||
log.info("未设置RedisAddress, 将使用默认值连接Redis服务器(127.0.0.1:6379)");
|
||||
System.setProperty("cgj.redisAddress", "127.0.0.1");
|
||||
}
|
||||
|
||||
File cookieStoreFile = new File("cookies.store");
|
||||
File cookieStoreFile = new File(System.getProperty("cgj.botDataDir"), "cookies.store");
|
||||
if(!cookieStoreFile.exists()) {
|
||||
log.error("未找到cookies.store文件, 是否启动PixivLoginProxyServer? (yes/no)");
|
||||
log.warn("未找到cookies.store文件, 是否启动PixivLoginProxyServer? (yes/no)");
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
if(scanner.nextLine().equalsIgnoreCase("yes")) {
|
||||
if(scanner.nextLine().trim().equalsIgnoreCase("yes")) {
|
||||
startPixivLoginProxyServer();
|
||||
} else {
|
||||
System.exit(1);
|
||||
@ -103,6 +104,37 @@ public class Main {
|
||||
ArgumentsRunner.run(Main.class, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从ArgumentsProperties获取设置项到System Properties
|
||||
* @param prop ArgumentsProperties对象
|
||||
* @param key 设置项key
|
||||
* @param defaultValue 默认值
|
||||
* @return 如果成功从ArgumentsProperties获得设置项, 返回true, 如未找到(使用了defaultValue或null), 返回false;
|
||||
*/
|
||||
private static boolean getSettingToSysProp(ArgumentsProperties prop, String key, String defaultValue) {
|
||||
if(prop.containsKey(key)) {
|
||||
log.info("{}: {}", key, prop.getValue(key));
|
||||
System.setProperty("cgj." + key, prop.getValue(key));
|
||||
return true;
|
||||
} else {
|
||||
if(defaultValue != null) {
|
||||
System.setProperty("cgj." + key, defaultValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean getEnvSettingToSysProp(String envKey, String sysPropKey, String defaultValue) {
|
||||
String env = System.getenv(envKey);
|
||||
if(env != null) {
|
||||
System.setProperty("cgj." + sysPropKey, env);
|
||||
return true;
|
||||
} else if(defaultValue != null) {
|
||||
System.setProperty("cgj." + sysPropKey, defaultValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Command
|
||||
public static void botMode(@Argument(name = "args", force = false) String argsStr) {
|
||||
new MiraiMain().init();
|
||||
@ -110,10 +142,10 @@ public class Main {
|
||||
|
||||
@Command
|
||||
public static void pluginMode(@Argument(name = "args", force = false) String argsStr) {
|
||||
if(!System.getProperty("cgj.cqRootDir").endsWith("\\") && !System.getProperty("cgj.cqRootDir").endsWith("/")) {
|
||||
System.setProperty("cgj.cqRootDir", System.getProperty("cgj.cqRootDir") + "/");
|
||||
if(!System.getProperty("cgj.botDataDir").endsWith("\\") && !System.getProperty("cgj.botDataDir").endsWith("/")) {
|
||||
System.setProperty("cgj.botDataDir", System.getProperty("cgj.botDataDir") + "/");
|
||||
}
|
||||
log.info("酷Q机器人根目录: {}", System.getProperty("cgj.cqRootDir"));
|
||||
log.info("酷Q机器人根目录: {}", System.getProperty("cgj.botDataDir"));
|
||||
CQConfig.init();
|
||||
Pattern pattern = Pattern.compile("/\\s*(\".+?\"|[^:\\s])+((\\s*:\\s*(\".+?\"|[^\\s])+)|)|(\".+?\"|[^\"\\s])+");
|
||||
Matcher matcher = pattern.matcher(Strings.nullToEmpty(argsStr));
|
||||
@ -353,89 +385,19 @@ public class Main {
|
||||
builder,
|
||||
PixivURL.getPixivRefererLink(illustId)
|
||||
);
|
||||
|
||||
/*log.info("正在下载...");
|
||||
List<String> list = PixivDownload.getIllustAllPageDownload(
|
||||
HttpClientBuilder.create()
|
||||
.setProxy(proxy)
|
||||
.build(),
|
||||
illustId, PixivDownload.PageQuality.ORIGINAL);*/
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
if(searchBuilder.getSearchArea().equals(PixivSearchBuilder.SearchArea.TOP)) {
|
||||
|
||||
} else {
|
||||
JsonArray illustsArray = resultBody
|
||||
.getAsJsonObject(searchBuilder.getSearchArea().jsonKey).getAsJsonArray("data");
|
||||
log.info("已找到与 {} 相关插图信息:", content);
|
||||
int count = 1;
|
||||
for (JsonElement jsonElement : illustsArray) {
|
||||
JsonObject illustObj = jsonElement.getAsJsonObject();
|
||||
//TODO: 防止数据内混入无效内容, 需要检查对象是否有illustId
|
||||
if(!illustObj.has("illustId")) {
|
||||
continue;
|
||||
}
|
||||
int illustId = illustObj.get("illustId").getAsInt();
|
||||
StringBuilder builder = new StringBuilder("[");
|
||||
illustObj.get("tags").getAsJsonArray().forEach(el -> builder.append(el.getAsString()).append(", "));
|
||||
builder.replace(builder.length() - 2, builder.length(), "]");
|
||||
log.info("({} / {})\n\t作品id: {}, \n\t作者名(作者id): {} ({}), \n\t作品标题: {}, \n\t作品Tags: {}, \n\t作品链接: {}",
|
||||
count++,
|
||||
illustsArray.size(),
|
||||
illustId,
|
||||
illustObj.get("userName").getAsString(),
|
||||
illustObj.get("userId").getAsInt(),
|
||||
illustObj.get("illustTitle").getAsString(),
|
||||
builder,
|
||||
PixivURL.getPixivRefererLink(illustId)
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@Command(defaultCommand = true)
|
||||
public static void testRun() {
|
||||
/*loadCookieStoreFromFile();
|
||||
|
||||
if(cookieStore == null){
|
||||
startPixivLoginProxyServer();
|
||||
}*/
|
||||
|
||||
//accessPixivToFile();
|
||||
|
||||
//startPixivAccessProxyServer();
|
||||
|
||||
//saveCookieStoreToFile();
|
||||
log.info("这里啥都没有哟w");
|
||||
}
|
||||
|
||||
private static void loadCookieStoreFromFile() throws IOException {
|
||||
log.info("正在加载CookieStore...");
|
||||
File storeFile = new File("./cookies.store");
|
||||
if(!storeFile.exists()){
|
||||
log.info("未找到CookieStore, 跳过加载.");
|
||||
return;
|
||||
}
|
||||
ObjectInputStream stream = new ObjectInputStream(new FileInputStream(storeFile));
|
||||
Object result;
|
||||
try {
|
||||
result = stream.readObject();
|
||||
} catch (ClassNotFoundException e) {
|
||||
log.error("加载出错", e);
|
||||
return;
|
||||
}
|
||||
cookieStore = (CookieStore) result;
|
||||
cookieStore.getCookies().forEach(cookie -> log.debug(cookie.getName() + ": " + cookie.getValue() + ", isExpired: " + cookie.isExpired(new Date())));
|
||||
log.info("CookieStore加载完成.");
|
||||
}
|
||||
|
||||
private static void saveCookieStoreToFile() throws IOException {
|
||||
log.info("正在保存CookieStore...");
|
||||
File outputFile = new File("./cookies.store");
|
||||
File outputFile = new File(System.getProperty("cgj.botDataDir"), "cookies.store");
|
||||
if(!outputFile.exists() && !outputFile.delete() && !outputFile.createNewFile()){
|
||||
log.error("保存CookieStore失败.");
|
||||
return;
|
||||
@ -482,75 +444,4 @@ public class Main {
|
||||
}
|
||||
}
|
||||
|
||||
private static void accessPixivToFile() throws IOException {
|
||||
File cookieStoreFile = new File("./cookie.txt");
|
||||
if (!cookieStoreFile.exists() && !cookieStoreFile.createNewFile()) {
|
||||
log.info("Cookie文件存储失败");
|
||||
}
|
||||
/*log.info("正在写出Cookie, Cookie count: " + cookieStore.getCookies().size());
|
||||
FileWriter cookieWriter = new FileWriter(cookieStoreFile);
|
||||
cookieStore.getCookies().forEach(cookie -> {
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder().append(cookie.getName()).append(" = ").append(cookie.getValue());
|
||||
log.info("正在导出Cookie: " + sb.toString());
|
||||
cookieWriter.append(sb.toString()).append("\n").flush();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
log.info("Cookie写出完成");*/
|
||||
|
||||
log.info("尝试通过捕获的Cookie访问Pixiv...");
|
||||
HttpClient httpClient = new PixivSession(new HttpHost("127.0.0.1", 1080), cookieStore).getHttpClient();
|
||||
HttpGet request = new HttpGet("https://www.pixiv.net");
|
||||
request.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0");
|
||||
request.addHeader(new BasicHeader("accept-encoding", "gzip, deflate, br"));
|
||||
request.addHeader(new BasicHeader("accept-language", "zh-CN,zh;q=0.9"));
|
||||
StringBuilder cookieBuilder = new StringBuilder();
|
||||
cookieStore.getCookies().forEach(cookie -> {
|
||||
if(cookie.isExpired(new Date())){
|
||||
return;
|
||||
}
|
||||
cookieBuilder.append(cookie.getName()).append("=").append(cookie.getValue()).append("; ");
|
||||
});
|
||||
request.addHeader("cookie", cookieBuilder.toString());
|
||||
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
log.info("正在写入文件...");
|
||||
File outFile = new File("./pixiv.html");
|
||||
if (outFile.createNewFile() && !outFile.exists()) {
|
||||
log.info("文件创建失败!");
|
||||
}else {
|
||||
new FileWriter(outFile).append(EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8)).flush();
|
||||
}
|
||||
|
||||
Pixiv pixiv = new Pixiv(httpClient);
|
||||
pixiv.getRecommend().forEach(illustMap -> {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
illustMap.forEach((key, value) -> builder.append(key).append(": ").append(value).append("\n"));
|
||||
try {
|
||||
builder.append("download Link: ").append(Arrays.toString(pixiv.getAllDownloadLink(Integer.parseInt(illustMap.get(Pixiv.ATTR_ILLUST_ID)))));
|
||||
} catch (IOException e) {
|
||||
log.error("获取下载链接时出错!", e);
|
||||
}
|
||||
log.info(builder.append("\n").toString());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private static void startPixivAccessProxyServer(){
|
||||
log.info("正在启动访问代理服务器, 将浏览器相关缓存清空后, 使用浏览器进行访问以尝试Cookie正确性.");
|
||||
final PixivAccessProxyServer accessProxyServer = new PixivAccessProxyServer(cookieStore, new ProxyConfig(ProxyType.SOCKS5, "127.0.0.1", 1080));
|
||||
Thread accessProxyServerThread = new Thread(() -> {
|
||||
log.info("正在启动PAPS...");
|
||||
accessProxyServer.start(1007);
|
||||
log.info("PAPS已关闭.");
|
||||
});
|
||||
accessProxyServerThread.setName("AccessProxyServerThread");
|
||||
accessProxyServerThread.start();
|
||||
new Scanner(System.in).nextLine();
|
||||
log.info("关闭PAPS服务器...");
|
||||
accessProxyServer.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,9 +22,9 @@ public class BotAdminCommandProcess {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(BotAdminCommandProcess.class.getSimpleName());
|
||||
|
||||
private final static File globalPropFile = new File("global.properties");
|
||||
private final static File globalPropFile = new File(System.getProperty("cgj.botDataDir"), "global.properties");
|
||||
|
||||
private final static File pushListFile = new File("pushList.json");
|
||||
private final static File pushListFile = new File(System.getProperty("cgj.botDataDir"), "pushList.json");
|
||||
|
||||
private final static Hashtable<Long, JsonObject> pushInfoMap = new Hashtable<>();
|
||||
|
||||
|
@ -35,13 +35,14 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "SameParameterValue"})
|
||||
public class BotCommandProcess {
|
||||
|
||||
private final static PixivDownload pixivDownload = new PixivDownload(Main.cookieStore, Main.proxy);
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(BotCommandProcess.class.getSimpleName());
|
||||
|
||||
private final static File imageStoreDir = new File(System.getProperty("cgj.cqRootDir"), "data/image/cgj/");
|
||||
private final static File imageStoreDir = new File(System.getProperty("cgj.botDataDir"), "data/image/cgj/");
|
||||
public final static Properties globalProp = new Properties();
|
||||
private final static Gson gson = new GsonBuilder()
|
||||
.serializeNulls()
|
||||
@ -76,7 +77,7 @@ public class BotCommandProcess {
|
||||
public static void initialize() {
|
||||
log.info("正在初始化...");
|
||||
|
||||
File globalPropFile = new File("./global.properties");
|
||||
File globalPropFile = new File(System.getProperty("cgj.botDataDir"), "global.properties");
|
||||
if(globalPropFile.exists() && globalPropFile.isFile()) {
|
||||
log.info("正在加载全局配置文件...");
|
||||
try {
|
||||
@ -244,7 +245,6 @@ public class BotCommandProcess {
|
||||
log.warn("配置项 {} 的参数值格式有误!", imageLimitPropertyKey);
|
||||
}
|
||||
|
||||
//TODO(LamGC, 2020.4.11): 将JsonRedisCacheStore更改为使用Redis的List集合, 以提高性能
|
||||
List<JsonObject> rankingInfoList = getRankingInfoByCache(type, mode, queryDate, 1, Math.max(0, itemLimit), false);
|
||||
if(rankingInfoList.isEmpty()) {
|
||||
return "无法查询排行榜,可能排行榜尚未更新。";
|
||||
@ -590,7 +590,7 @@ public class BotCommandProcess {
|
||||
illustPreLoadDataCache.clear();
|
||||
pagesCache.clear();
|
||||
searchBodyCache.clear();
|
||||
File imageStoreDir = new File(System.getProperty("cgj.cqRootDir") + "data/image/cgj/");
|
||||
File imageStoreDir = new File(System.getProperty("cgj.botDataDir") + "data/image/cgj/");
|
||||
File[] listFiles = imageStoreDir.listFiles();
|
||||
if (listFiles == null) {
|
||||
log.debug("图片缓存目录为空或内部文件获取失败!");
|
||||
@ -655,7 +655,7 @@ public class BotCommandProcess {
|
||||
String illustIdStr = buildSyncKey(Integer.toString(illustId));
|
||||
JsonObject illustInfoObj = null;
|
||||
if (!illustInfoCache.exists(illustIdStr) || flushCache) {
|
||||
synchronized (illustIdStr) { // TODO: 这里要不做成HashMap存储key而避免使用常量池?
|
||||
synchronized (illustIdStr) {
|
||||
if (!illustInfoCache.exists(illustIdStr) || flushCache) {
|
||||
illustInfoObj = pixivDownload.getIllustInfoByIllustId(illustId);
|
||||
illustInfoCache.update(illustIdStr, illustInfoObj, null);
|
||||
@ -684,7 +684,7 @@ public class BotCommandProcess {
|
||||
if (!illustPreLoadDataCache.exists(illustIdStr) || flushCache) {
|
||||
synchronized (illustIdStr) {
|
||||
if (!illustPreLoadDataCache.exists(illustIdStr) || flushCache) {
|
||||
log.info("IllustId {} 缓存失效, 正在更新...", illustId);
|
||||
log.debug("IllustId {} 缓存失效, 正在更新...", illustId);
|
||||
JsonObject preLoadDataObj = pixivDownload.getIllustPreLoadDataById(illustId)
|
||||
.getAsJsonObject("illust")
|
||||
.getAsJsonObject(Integer.toString(illustId));
|
||||
@ -700,7 +700,7 @@ public class BotCommandProcess {
|
||||
|
||||
result = preLoadDataObj;
|
||||
illustPreLoadDataCache.update(illustIdStr, preLoadDataObj, expire);
|
||||
log.info("作品Id {} preLoadData缓存已更新(有效时间: {})", illustId, expire);
|
||||
log.debug("作品Id {} preLoadData缓存已更新(有效时间: {})", illustId, expire);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -772,15 +772,16 @@ public class BotCommandProcess {
|
||||
if(!rankingCache.exists(requestSign) || flushCache) {
|
||||
synchronized(requestSign) {
|
||||
if(!rankingCache.exists(requestSign) || flushCache) {
|
||||
log.info("Ranking缓存失效, 正在更新...(RequestSign: {})", requestSign);
|
||||
log.debug("Ranking缓存失效, 正在更新...(RequestSign: {})", requestSign);
|
||||
List<JsonObject> rankingResult = pixivDownload.getRanking(contentType, mode, queryDate, 1, 500);
|
||||
long expireTime = 0;
|
||||
if(rankingResult.size() == 0) {
|
||||
log.info("数据获取失败, 将设置浮动有效时间以准备下次更新.");
|
||||
expireTime = 5400000 + expireTimeFloatRandom.nextInt(1800000);
|
||||
log.warn("数据获取失败, 将设置浮动有效时间以准备下次更新. (ExpireTime: {}ms)", expireTime);
|
||||
}
|
||||
result = new ArrayList<>(rankingResult).subList(start - 1, start + range - 1);
|
||||
rankingCache.update(requestSign, rankingResult,
|
||||
rankingResult.size() == 0 ? 5400000 + expireTimeFloatRandom.nextInt(1800000) : 0);
|
||||
log.info("Ranking缓存更新完成.(RequestSign: {})", requestSign);
|
||||
rankingCache.update(requestSign, rankingResult, expireTime);
|
||||
log.debug("Ranking缓存更新完成.(RequestSign: {})", requestSign);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
package net.lamgc.cgj.bot;
|
||||
|
||||
import net.lamgc.cgj.bot.event.MessageEvent;
|
||||
import net.lamgc.cgj.bot.event.VirtualLoadMessageEvent;
|
||||
import net.lamgc.utils.event.EventExecutor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 消息事件处理调试器.
|
||||
* <p>当启用了消息事件处理调试后, 将会根据调试器代号调用指定调试器</p>
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public enum MessageEventExecutionDebugger {
|
||||
|
||||
/**
|
||||
* PM - 压力测试
|
||||
*/
|
||||
PM ((executor, event, properties, log) -> {
|
||||
MessageEvent virtualLoadEvent = VirtualLoadMessageEvent.toVirtualLoadMessageEvent(event, false);
|
||||
int rotation = 5;
|
||||
int number = 50;
|
||||
int interval = 2500;
|
||||
|
||||
try {
|
||||
rotation = Integer.parseInt(properties.getProperty("debug.pm.rotation", "5"));
|
||||
} catch(NumberFormatException ignored) {}
|
||||
try {
|
||||
number = Integer.parseInt(properties.getProperty("debug.pm.number", "50"));
|
||||
} catch(NumberFormatException ignored) {}
|
||||
try {
|
||||
interval = Integer.parseInt(properties.getProperty("debug.pm.interval", "2500"));
|
||||
} catch(NumberFormatException ignored) {}
|
||||
|
||||
boolean interrupted = false;
|
||||
Thread currentThread = Thread.currentThread();
|
||||
for(int rotationCount = 0; rotationCount < rotation && !interrupted; rotationCount++) {
|
||||
for(int sendCount = 0; sendCount < number; sendCount++) {
|
||||
if(currentThread.isInterrupted()) {
|
||||
interrupted = true;
|
||||
break;
|
||||
}
|
||||
executor.executor(virtualLoadEvent);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(interval);
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public final static String debuggerLoggerNameTemp = "MessageEventExecuteDebugger-{debugger}";
|
||||
|
||||
public final MessageExecuteDebugger debugger;
|
||||
|
||||
MessageEventExecutionDebugger(MessageExecuteDebugger debugger) {
|
||||
this.debugger = debugger;
|
||||
}
|
||||
|
||||
public static Logger getDebuggerLogger(MessageEventExecutionDebugger debugger) {
|
||||
return LoggerFactory.getLogger(debuggerLoggerNameTemp.replace("{debugger}", debugger.name()));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface MessageExecuteDebugger {
|
||||
/**
|
||||
* 接收事件并根据指定需求处理
|
||||
* @param executor 事件执行器
|
||||
* @param event 消息事件对象
|
||||
* @param properties 配置项, 调试器应按'debug.[debuggerName].'为前缀存储相应调试信息
|
||||
* @throws Exception 当抛出异常则打断调试, 并输出至日志
|
||||
*/
|
||||
void accept(EventExecutor executor, MessageEvent event, Properties properties, Logger logger) throws Exception;
|
||||
}
|
||||
|
||||
}
|
@ -69,7 +69,7 @@ public class RandomRankingArtworksSender extends AutoSender {
|
||||
selectRanking,
|
||||
1, false);
|
||||
|
||||
log.info("RankingResult.size: {}", rankingList.size());
|
||||
log.debug("RankingResult.size: {}", rankingList.size());
|
||||
if(rankingList.size() != 1) {
|
||||
log.error("排行榜选取失败!(获取到了多个结果)");
|
||||
return;
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.lamgc.cgj.bot;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import net.lamgc.cgj.bot.event.BotEventHandler;
|
||||
import net.lamgc.cgj.bot.event.VirtualLoadMessageEvent;
|
||||
import net.lamgc.cgj.pixiv.PixivURL;
|
||||
@ -28,10 +27,10 @@ public class RankingUpdateTimer {
|
||||
cal.setTime(firstRunDate == null ? new Date() : firstRunDate);
|
||||
LocalDate currentLocalDate = LocalDate.now();
|
||||
if(cal.get(Calendar.DAY_OF_YEAR) <= currentLocalDate.getDayOfYear() && cal.get(Calendar.HOUR_OF_DAY) >= 12) {
|
||||
cal.set(Calendar.DAY_OF_YEAR, cal.get(Calendar.DAY_OF_YEAR) + 1);
|
||||
cal.set(Calendar.DAY_OF_YEAR, currentLocalDate.getDayOfYear() + 1);
|
||||
}
|
||||
cal.set(Calendar.HOUR_OF_DAY, 12);
|
||||
cal.set(Calendar.MINUTE, 30);
|
||||
cal.set(Calendar.MINUTE, 0);
|
||||
cal.set(Calendar.SECOND, 0);
|
||||
cal.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
@ -66,15 +65,9 @@ public class RankingUpdateTimer {
|
||||
log.debug("不支持的类型, 填空值跳过...(类型: {}.{})", rankingMode.name(), contentType.name());
|
||||
}
|
||||
log.info("当前排行榜类型: {}.{}, 正在更新...", rankingMode.name(), contentType.name());
|
||||
try {
|
||||
//BotCommandProcess.getRankingInfoByCache(contentType, rankingMode, calendar.getTime(), 1, 0, true);
|
||||
BotEventHandler.executor.executorSync(
|
||||
new VirtualLoadMessageEvent(0,0,
|
||||
".cgj ranking -type=" + contentType.name() + " -mode=" + rankingMode.name()));
|
||||
log.info("排行榜 {}.{} 更新完成.", rankingMode.name(), contentType.name());
|
||||
} catch (InterruptedException e) {
|
||||
log.error("排行榜 {}.{} 更新时发生异常. \n{}", rankingMode.name(), contentType.name(), Throwables.getStackTraceAsString(e));
|
||||
}
|
||||
BotEventHandler.executeMessageEvent(new VirtualLoadMessageEvent(0,0,
|
||||
".cgj ranking -type=" + contentType.name() + " -mode=" + rankingMode.name()));
|
||||
log.info("排行榜 {}.{} 负载指令已投递.", rankingMode.name(), contentType.name());
|
||||
}
|
||||
}
|
||||
log.warn("定时任务更新完成.");
|
||||
|
@ -30,7 +30,7 @@ public class ImageCacheHandler implements EventHandler {
|
||||
@SuppressWarnings("unused")
|
||||
public void getImageToCache(ImageCacheObject event) {
|
||||
if(cacheQueue.contains(event)) {
|
||||
log.info("图片 {} 已存在相同缓存任务, 跳过.", event.getStoreFile().getName());
|
||||
log.debug("图片 {} 已存在相同缓存任务, 跳过.", event.getStoreFile().getName());
|
||||
return;
|
||||
} else {
|
||||
cacheQueue.add(event);
|
||||
@ -39,7 +39,7 @@ public class ImageCacheHandler implements EventHandler {
|
||||
try {
|
||||
log.info("图片 {} Event正在进行...({})", event.getStoreFile().getName(), Integer.toHexString(event.hashCode()));
|
||||
File storeFile = event.getStoreFile();
|
||||
log.info("正在缓存图片 {} (Path: {})", storeFile.getName(), storeFile.getAbsolutePath());
|
||||
log.debug("正在缓存图片 {} (Path: {})", storeFile.getName(), storeFile.getAbsolutePath());
|
||||
try {
|
||||
if(!storeFile.exists() && !storeFile.createNewFile()) {
|
||||
log.error("无法创建文件(Path: {})", storeFile.getAbsolutePath());
|
||||
@ -64,7 +64,7 @@ public class ImageCacheHandler implements EventHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("正在下载...(Content-Length: {}KB)", response.getEntity().getContentLength() / 1024);
|
||||
log.debug("正在下载...(Content-Length: {}KB)", response.getEntity().getContentLength() / 1024);
|
||||
try(FileOutputStream fos = new FileOutputStream(storeFile)) {
|
||||
IOUtils.copy(response.getEntity().getContent(), fos);
|
||||
} catch (IOException e) {
|
||||
|
@ -82,9 +82,7 @@ public abstract class RedisPoolCacheStore<T> implements CacheStore<T> {
|
||||
@Override
|
||||
public boolean clear() {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
String result = jedis.flushDB();
|
||||
log.info("flushDB返回结果: {}", result);
|
||||
return true;
|
||||
return jedis.flushDB().equalsIgnoreCase("ok");
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,13 +156,4 @@ public abstract class RedisPoolCacheStore<T> implements CacheStore<T> {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换原本的分隔符(.)为(:).<br/>
|
||||
* 即将启用
|
||||
* @param key 要处理的键名
|
||||
* @return 处理后的键名
|
||||
*/
|
||||
public static String replaceKey(String key) {
|
||||
return key.replaceAll("\\.", ":");
|
||||
}
|
||||
}
|
||||
|
@ -5,17 +5,17 @@ import com.google.common.base.Throwables;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import net.lamgc.cgj.bot.BotAdminCommandProcess;
|
||||
import net.lamgc.cgj.bot.BotCommandProcess;
|
||||
import net.lamgc.cgj.bot.MessageEventExecutionDebugger;
|
||||
import net.lamgc.cgj.util.DateParser;
|
||||
import net.lamgc.cgj.util.PagesQualityParser;
|
||||
import net.lamgc.cgj.util.TimeLimitThreadPoolExecutor;
|
||||
import net.lamgc.utils.base.runner.ArgumentsRunner;
|
||||
import net.lamgc.utils.base.runner.ArgumentsRunnerConfig;
|
||||
import net.lamgc.utils.base.runner.exception.DeveloperRunnerException;
|
||||
import net.lamgc.utils.base.runner.exception.NoSuchCommandException;
|
||||
import net.lamgc.utils.base.runner.exception.ParameterNoFoundException;
|
||||
import net.lamgc.utils.event.EventExecutor;
|
||||
import net.lamgc.utils.event.EventHandler;
|
||||
import net.lamgc.utils.event.*;
|
||||
import net.lamgc.utils.event.EventObject;
|
||||
import net.lamgc.utils.event.EventUncaughtExceptionHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
@ -25,7 +25,6 @@ import java.net.URI;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.regex.Matcher;
|
||||
@ -50,9 +49,10 @@ public class BotEventHandler implements EventHandler {
|
||||
/**
|
||||
* 消息事件执行器
|
||||
*/
|
||||
public final static EventExecutor executor = new EventExecutor(new ThreadPoolExecutor(
|
||||
(int) Math.ceil(Runtime.getRuntime().availableProcessors() / 2F),
|
||||
Runtime.getRuntime().availableProcessors(),
|
||||
private final static EventExecutor executor = new EventExecutor(new TimeLimitThreadPoolExecutor(
|
||||
60 * 1000,
|
||||
Math.max(Runtime.getRuntime().availableProcessors(), 4),
|
||||
Math.max(Math.max(Runtime.getRuntime().availableProcessors() * 2, 4), 32),
|
||||
30L,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(1536),
|
||||
@ -124,6 +124,29 @@ public class BotEventHandler implements EventHandler {
|
||||
BotCommandProcess.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 投递消息事件
|
||||
* @param event 事件对象
|
||||
*/
|
||||
@NotAccepted
|
||||
public static void executeMessageEvent(MessageEvent event) {
|
||||
String debuggerName;
|
||||
if(!event.getMessage().startsWith(ADMIN_COMMAND_PREFIX) &&
|
||||
!Strings.isNullOrEmpty(debuggerName = BotCommandProcess.globalProp.getProperty("debug.debugger"))) {
|
||||
try {
|
||||
MessageEventExecutionDebugger debugger = MessageEventExecutionDebugger.valueOf(debuggerName.toUpperCase());
|
||||
debugger.debugger.accept(executor, event, BotCommandProcess.globalProp,
|
||||
MessageEventExecutionDebugger.getDebuggerLogger(debugger));
|
||||
} catch(IllegalArgumentException e) {
|
||||
log.warn("未找到指定调试器: '{}'", debuggerName);
|
||||
} catch (Exception e) {
|
||||
log.error("事件调试处理时发生异常", e);
|
||||
}
|
||||
} else {
|
||||
BotEventHandler.executor.executor(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 以事件形式处理消息事件
|
||||
* @param event 消息事件对象
|
||||
@ -185,10 +208,15 @@ public class BotEventHandler implements EventHandler {
|
||||
} catch(ParameterNoFoundException e) {
|
||||
result = "命令缺少参数: " + e.getParameterName();
|
||||
} catch(DeveloperRunnerException e) {
|
||||
log.error("执行命令时发生异常", e);
|
||||
result = "命令执行时发生错误,无法完成!";
|
||||
if (!(e.getCause() instanceof InterruptedException)) {
|
||||
log.error("执行命令时发生异常", e);
|
||||
result = "色图姬在执行命令时遇到了一个错误!";
|
||||
} else {
|
||||
log.error("命令执行超时, 终止执行.");
|
||||
result = "色图姬发现这个命令的处理时间太久了!所以打断了这个命令。";
|
||||
}
|
||||
}
|
||||
log.info("命令处理完成.(耗时: {}ms)", System.currentTimeMillis() - time);
|
||||
long processTime = System.currentTimeMillis() - time;
|
||||
if(Objects.requireNonNull(result) instanceof String) {
|
||||
try {
|
||||
event.sendMessage((String) result);
|
||||
@ -196,7 +224,10 @@ public class BotEventHandler implements EventHandler {
|
||||
log.error("发送消息时发生异常", e);
|
||||
}
|
||||
}
|
||||
log.info("命令反馈完成.(耗时: {}ms)", System.currentTimeMillis() - time);
|
||||
long totalTime = System.currentTimeMillis() - time;
|
||||
log.info("命令反馈完成.(事件耗时: {}ms, P: {}%({}ms), R: {}%({}ms))", totalTime,
|
||||
String.format("%.3f", ((double) processTime / (double)totalTime) * 100F), processTime,
|
||||
String.format("%.3f", ((double) (totalTime - processTime) / (double)totalTime) * 100F), totalTime - processTime);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,6 +5,28 @@ package net.lamgc.cgj.bot.event;
|
||||
*/
|
||||
public class VirtualLoadMessageEvent extends MessageEvent {
|
||||
|
||||
/**
|
||||
* 将任意消息事件转换为假负载消息事件.
|
||||
* <p>转换之后, 除了fromGroup, fromQQ, message外其他信息不会保留</p>
|
||||
* @param event 待转换的消息事件
|
||||
* @param inheritImpl 是否继承除 sendMessage 外的其他 MessageEvent 实现
|
||||
* @return 转换后的消息事件
|
||||
*/
|
||||
public static VirtualLoadMessageEvent toVirtualLoadMessageEvent(MessageEvent event, boolean inheritImpl) {
|
||||
if(event instanceof VirtualLoadMessageEvent) {
|
||||
return (VirtualLoadMessageEvent) event;
|
||||
} else if(!inheritImpl) {
|
||||
return new VirtualLoadMessageEvent(event.getFromGroup(), event.getFromQQ(), event.getMessage());
|
||||
} else {
|
||||
return new VirtualLoadMessageEvent(event.getFromGroup(), event.getFromQQ(), event.getMessage()) {
|
||||
@Override
|
||||
public String getImageUrl(String image) {
|
||||
return event.getImageUrl(image);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public VirtualLoadMessageEvent(long fromGroup, long fromQQ, String message) {
|
||||
super(fromGroup, fromQQ, message);
|
||||
}
|
||||
|
@ -24,19 +24,16 @@ public class CQPluginMain extends CQPlugin implements EventHandler {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@ -44,7 +41,7 @@ public class CQPluginMain extends CQPlugin implements EventHandler {
|
||||
if(!BotEventHandler.match(event.getMessage())) {
|
||||
return MESSAGE_IGNORE;
|
||||
}
|
||||
BotEventHandler.executor.executor(new SpringCQMessageEvent(cq, event));
|
||||
BotEventHandler.executeMessageEvent(new SpringCQMessageEvent(cq, event));
|
||||
return MESSAGE_BLOCK;
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ public class MiraiMain implements Closeable {
|
||||
return;
|
||||
}
|
||||
|
||||
File botPropFile = new File("./bot.properties");
|
||||
File botPropFile = new File(System.getProperty("cgj.botDataDir"), "./bot.properties");
|
||||
try (Reader reader = new BufferedReader(new FileReader(botPropFile))) {
|
||||
botProperties.load(reader);
|
||||
} catch (IOException e) {
|
||||
@ -43,8 +43,8 @@ public class MiraiMain implements Closeable {
|
||||
}
|
||||
|
||||
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)));
|
||||
Events.subscribeAlways(GroupMessage.class, (msg) -> BotEventHandler.executeMessageEvent(new MiraiMessageEvent(msg)));
|
||||
Events.subscribeAlways(FriendMessage.class, (msg) -> BotEventHandler.executeMessageEvent(new MiraiMessageEvent(msg)));
|
||||
bot.login();
|
||||
MessageSenderBuilder.setCurrentMessageSenderFactory(new MiraiMessageSenderFactory(bot));
|
||||
BotEventHandler.preLoad();
|
||||
|
@ -111,7 +111,8 @@ public class MiraiMessageSender implements MessageSender {
|
||||
} else {
|
||||
return MessageUtils.newChain("(参数不存在)");
|
||||
}
|
||||
if(code.getParameter("flashImage").equalsIgnoreCase("true")) {
|
||||
if(Strings.nullToEmpty(code.getParameter("flashImage"))
|
||||
.equalsIgnoreCase("true")) {
|
||||
return MessageUtils.flash(img);
|
||||
} else {
|
||||
return img;
|
||||
@ -155,7 +156,7 @@ public class MiraiMessageSender implements MessageSender {
|
||||
}
|
||||
}
|
||||
imageIdCache.update(imageName, image.getImageId(), expireTime);
|
||||
log.info("imageName [{}] 缓存更新完成.(有效时间: {})", imageName, expireTime);
|
||||
log.debug("imageName [{}] 缓存更新完成.(有效时间: {})", imageName, expireTime);
|
||||
} else {
|
||||
log.debug("ImageName: [{}] 缓存命中.", imageName);
|
||||
}
|
||||
|
@ -1,69 +0,0 @@
|
||||
package net.lamgc.cgj.pixiv;
|
||||
|
||||
/**
|
||||
* 插图(集)的信息
|
||||
*/
|
||||
public class IllustInfo{
|
||||
|
||||
/**
|
||||
* 插图(集)ID
|
||||
*/
|
||||
public final int illustID;
|
||||
|
||||
/**
|
||||
* 第几页
|
||||
*/
|
||||
public final int page;
|
||||
|
||||
/**
|
||||
* 插图标题
|
||||
*/
|
||||
public final String title;
|
||||
|
||||
/**
|
||||
* 插图说明
|
||||
*/
|
||||
public final String description;
|
||||
|
||||
/**
|
||||
* 插图标签
|
||||
*/
|
||||
public final String[] tags;
|
||||
|
||||
/**
|
||||
* 插图图片长度
|
||||
*/
|
||||
public final int width;
|
||||
|
||||
/**
|
||||
* 插图图片高度
|
||||
*/
|
||||
public final int height;
|
||||
|
||||
/**
|
||||
* 作者名
|
||||
*/
|
||||
public final String authorName;
|
||||
|
||||
/**
|
||||
* 作者用户ID
|
||||
*/
|
||||
public final int authorUserID;
|
||||
|
||||
public IllustInfo(int illustID, String title, String description, String[] tags, int width, int height, String authorName, int authorUserID){
|
||||
this(illustID, 0, title, description, tags, width, height, authorName, authorUserID);
|
||||
}
|
||||
|
||||
public IllustInfo(int illustID, int p, String title, String description, String[] tags, int width, int height, String authorName, int authorUserID){
|
||||
this.illustID = illustID;
|
||||
this.page = p;
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.tags = tags;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.authorName = authorName;
|
||||
this.authorUserID = authorUserID;
|
||||
}
|
||||
|
||||
}
|
@ -1,267 +0,0 @@
|
||||
package net.lamgc.cgj.pixiv;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.conn.ConnectionPoolTimeoutException;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
public class Pixiv {
|
||||
|
||||
/**
|
||||
* illust Link
|
||||
*/
|
||||
public final static String ATTR_LINK = "link";
|
||||
|
||||
/**
|
||||
* illust Id
|
||||
*/
|
||||
public final static String ATTR_ILLUST_ID = "illustId";
|
||||
|
||||
/**
|
||||
* illust Title
|
||||
*/
|
||||
public final static String ATTR_TITLE = "title";
|
||||
|
||||
/**
|
||||
* illust Author Name
|
||||
*/
|
||||
public final static String ATTR_AUTHOR_NAME = "authorName";
|
||||
|
||||
/**
|
||||
* illust Author UserID
|
||||
*/
|
||||
public final static String ATTR_AUTHOR_ID = "authorId";
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public Pixiv(HttpClient client){
|
||||
this.httpClient = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用帐号密码登录Pixiv
|
||||
* @param PixivID Pixiv账户登录名
|
||||
* @param Password Pixiv帐号密码
|
||||
* @throws IOException 当登录连接出现异常时抛出
|
||||
* @deprecated {@link PixivSession#Login(String, String)} 已经废除, 故本方法不可用
|
||||
*/
|
||||
public Pixiv(String PixivID, String Password) throws IOException {
|
||||
this(PixivID, Password, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用帐号密码登录Pixiv
|
||||
* @param PixivID Pixiv账户登录名
|
||||
* @param Password Pixiv帐号密码
|
||||
* @param proxy 代理设置
|
||||
* @throws IOException 当登录连接出现异常时抛出
|
||||
* @deprecated {@link PixivSession#Login(String, String)} 已经废除, 故本方法不可用
|
||||
*/
|
||||
public Pixiv(String PixivID, String Password, HttpHost proxy) throws IOException {
|
||||
PixivSession pixivSession = new PixivSession(proxy, null);
|
||||
if(pixivSession.Login(PixivID, Password)){
|
||||
System.out.println("P站登录成功!");
|
||||
}else{
|
||||
System.out.println("P站登录失败!错误信息: " + pixivSession.getErrMsg());
|
||||
throw new RuntimeException(pixivSession.getErrMsg());
|
||||
}
|
||||
//httpClient = pixivSession.getHttpClient();
|
||||
httpClient = HttpClientBuilder.create()
|
||||
.setDefaultCookieStore(pixivSession.getCookieStore())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取首页推荐列表
|
||||
* @return 首页推荐列表, 一个Map对应一个推荐项, 使用<code>ATTR_</code>开头常量访问即可
|
||||
* @throws IOException
|
||||
*/
|
||||
public List<Map<String, String>> getRecommend() throws IOException {
|
||||
HttpGet getRequest = new HttpGet(PixivURL.PIXIV_INDEX_URL);
|
||||
HttpResponse response = httpClient.execute(getRequest);
|
||||
String pageAsXML = EntityUtils.toString(response.getEntity(),"utf-8");
|
||||
|
||||
//获取推荐图列表(li)
|
||||
//System.out.println(pageAsXML);
|
||||
|
||||
Document document = Jsoup.parse(pageAsXML);
|
||||
|
||||
List<String> links = document.select("._image-items.gtm-illust-recommend-zone>li>.gtm-illust-recommend-thumbnail-link").eachAttr("href");
|
||||
List<String> illustId = document.select("._image-items.gtm-illust-recommend-zone>li>.gtm-illust-recommend-thumbnail-link").eachAttr("data-gtm-recommend-illust-id");
|
||||
List<String> title = document.select("._image-items.gtm-illust-recommend-zone>li>.gtm-illust-recommend-title>h1").eachAttr("title");
|
||||
List<String> authorName = document.select("._image-items.gtm-illust-recommend-zone>li>.gtm-illust-recommend-user-name").eachText();
|
||||
List<String> authorId = document.select("._image-items.gtm-illust-recommend-zone>li>.gtm-illust-recommend-user-name").eachAttr("data-user_id");
|
||||
|
||||
List<Map<String, String>> recommendList = new ArrayList<>();
|
||||
for(int i = 0; i < links.size(); i++){
|
||||
//System.out.println(links.get(i));
|
||||
Map<String, String> info = new HashMap<>();
|
||||
info.put(ATTR_LINK, PixivURL.PIXIV_INDEX_URL + links.get(i));
|
||||
info.put(ATTR_ILLUST_ID, illustId.get(i));
|
||||
info.put(ATTR_TITLE, title.get(i));
|
||||
info.put(ATTR_AUTHOR_NAME, authorName.get(i));
|
||||
info.put(ATTR_AUTHOR_ID, authorId.get(i));
|
||||
recommendList.add(info);
|
||||
}
|
||||
return recommendList;
|
||||
}
|
||||
|
||||
public String[] getAllDownloadLink(int illustID) throws IOException {
|
||||
HttpGet illustPage = new HttpGet(PixivURL.PIXIV_ILLUST_API_URL.replaceAll("\\{illustId}", String.valueOf(illustID)));
|
||||
HttpResponse response = httpClient.execute(illustPage);
|
||||
String pageAsXML = EntityUtils.toString(response.getEntity(),"utf-8");
|
||||
//System.out.println(pageAsXML);
|
||||
JsonObject resultObj = (JsonObject) new JsonParser().parse(pageAsXML);
|
||||
if(!resultObj.get("error").getAsBoolean()){
|
||||
JsonArray bodyArray = resultObj.get("body").getAsJsonArray();
|
||||
int length = bodyArray.size();
|
||||
String[] result = new String[length];
|
||||
for(int i = 0; i < length; i++){
|
||||
JsonObject childObj = bodyArray.get(i).getAsJsonObject();
|
||||
result[i] = childObj.get("urls").getAsJsonObject().get("original").getAsString();
|
||||
}
|
||||
return result;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 下载P站图片
|
||||
* @param illustID 插图ID
|
||||
* @return 成功返回图片输入流,失败或为多图则返回null
|
||||
*/
|
||||
public InputStream[] downloadIllustImage(int illustID) throws IOException {
|
||||
String[] links = getAllDownloadLink(illustID);
|
||||
List<InputStream> inputStreamList = new ArrayList<>();
|
||||
int count = 1;
|
||||
boolean retry = false;
|
||||
for(int i = 0; i < links.length; i++){
|
||||
try {
|
||||
long sleepTime = (new Random().nextInt(4) + 2) * 1000;
|
||||
System.out.println("nextTime: " + (float)(sleepTime / 1000));
|
||||
Thread.sleep(sleepTime);
|
||||
} catch (InterruptedException ignored) {}
|
||||
String link = links[i];
|
||||
System.out.print("page:" + count++ + "/" + links.length + " ...");
|
||||
HttpGet imgGet = new HttpGet(link);
|
||||
//关键!如果不加上Referer的话,会返回403
|
||||
imgGet.setHeader("Referer", PixivURL.PIXIV_ILLUST_MEDIUM_URL.replaceAll("\\{illustId}", String.valueOf(illustID)));
|
||||
RequestConfig config = RequestConfig.custom()
|
||||
.setConnectTimeout(20 * 1000)
|
||||
.setConnectionRequestTimeout(20 * 1000)
|
||||
.setSocketTimeout(30 * 1000)
|
||||
.build();
|
||||
imgGet.setConfig(config);
|
||||
HttpResponse response;
|
||||
try {
|
||||
response = httpClient.execute(imgGet);
|
||||
}catch(ConnectionPoolTimeoutException e){
|
||||
if(retry){
|
||||
retry = false;
|
||||
System.out.println("获取失败,跳过...");
|
||||
continue;
|
||||
}
|
||||
System.out.println("连接超时,重新获取...");
|
||||
retry = true;
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
retry = false;
|
||||
|
||||
ByteArrayOutputStream cacheOutputStream = new ByteArrayOutputStream((int)response.getEntity().getContentLength());
|
||||
InputStream content = response.getEntity().getContent();
|
||||
int readLength;
|
||||
byte[] cache = new byte[4096];
|
||||
while((readLength = content.read(cache)) != -1){
|
||||
cacheOutputStream.write(cache, 0, readLength);
|
||||
}
|
||||
byte[] data = cacheOutputStream.toByteArray();
|
||||
//System.out.println("读到数据: " + data.length);
|
||||
inputStreamList.add(new ByteArrayInputStream(data));
|
||||
System.out.println("done!(length: " + response.getEntity().getContentLength() + ")");
|
||||
|
||||
}
|
||||
return inputStreamList.toArray(new InputStream[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载P站图片
|
||||
* @param illustID 插图ID
|
||||
* @return 成功返回图片输入流,失败或为多图则返回null
|
||||
*/
|
||||
public InputStream downloadIllustImages(int illustID){
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过解析插图详情页获取
|
||||
* - 插图标题
|
||||
* - 插图作者(及其UserId)
|
||||
* - 插图上传时间
|
||||
* - 插图标签(原始标签)
|
||||
* ...
|
||||
* @return 成功返回IllustInfo对象,失败返回null
|
||||
*/
|
||||
public IllustInfo[] getIllustInfo(int[] illustIDs) throws IOException {
|
||||
//获取Api
|
||||
HttpGet apiRequest = new HttpGet(PixivURL.getPixivIllustInfoAPI(illustIDs));
|
||||
HttpResponse response = httpClient.execute(apiRequest);
|
||||
String resultText = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
System.out.println(resultText);
|
||||
JsonObject resultObj = ((JsonObject) new JsonParser().parse(resultText));
|
||||
if(resultObj.get("error").getAsBoolean()){
|
||||
System.err.println("获取失败!");
|
||||
return null;
|
||||
}
|
||||
List<IllustInfo> illustInfoList = new ArrayList<>();
|
||||
JsonArray illustArray = resultObj.get("body").getAsJsonObject().get("illusts").getAsJsonArray();
|
||||
illustArray.forEach(jsonElement -> {
|
||||
JsonObject illustInfoObj = jsonElement.getAsJsonObject();
|
||||
JsonArray tagsArray = illustInfoObj.get("tags").getAsJsonArray();
|
||||
String[] tags = new String[tagsArray.size()];
|
||||
for(int i = 0; i < tags.length; i++){
|
||||
tags[i] = tagsArray.get(i).getAsString();
|
||||
}
|
||||
//TODO: 通过不需要作者id就能获取图片信息的api无法获取图片尺寸
|
||||
IllustInfo illustInfo = new IllustInfo(
|
||||
illustInfoObj.get("workId").getAsInt(),
|
||||
illustInfoObj.get("title").getAsString(),
|
||||
null,
|
||||
tags,
|
||||
-1,
|
||||
-1,
|
||||
illustInfoObj.get("userName").getAsString(),
|
||||
illustInfoObj.get("userId").getAsInt()
|
||||
);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定用户的所有插画
|
||||
*/
|
||||
public void getUserAllIllustTest() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -285,7 +285,8 @@ public class PixivDownload {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取排行榜
|
||||
* 获取排行榜.
|
||||
* <p>注意: 如果范围实际上没超出, 但返回排行榜不足, 会导致与实际请求的数量不符, 需要检查</p>
|
||||
* @param contentType 排行榜类型
|
||||
* @param mode 排行榜模式
|
||||
* @param time 查询时间
|
||||
@ -316,7 +317,8 @@ public class PixivDownload {
|
||||
int count = 0;
|
||||
Gson gson = new Gson();
|
||||
ArrayList<JsonObject> results = new ArrayList<>(range);
|
||||
for (int pageIndex = startPages; pageIndex <= endPages && count < range; pageIndex++) {
|
||||
boolean canNext = true;
|
||||
for (int pageIndex = startPages; canNext && pageIndex <= endPages && count < range; pageIndex++) {
|
||||
HttpGet request = createHttpGetRequest(PixivURL.getRankingLink(contentType, mode, time, pageIndex, true));
|
||||
log.debug("RequestUri: {}", request.getURI());
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
@ -326,10 +328,13 @@ public class PixivDownload {
|
||||
throw new IOException("Http Response Error: '" + response.getStatusLine() + "', ResponseBody: '" + responseBody + '\'');
|
||||
}
|
||||
|
||||
JsonArray resultArray = gson.fromJson(responseBody, JsonObject.class).getAsJsonArray("contents");
|
||||
JsonObject resultObject = gson.fromJson(responseBody, JsonObject.class);
|
||||
canNext = resultObject.get("next").getAsJsonPrimitive().isNumber();
|
||||
JsonArray resultArray = resultObject.getAsJsonArray("contents");
|
||||
for (int resultIndex = startIndex; resultIndex < resultArray.size() && count < range; resultIndex++, count++) {
|
||||
results.add(resultArray.get(resultIndex).getAsJsonObject());
|
||||
}
|
||||
|
||||
// 重置索引
|
||||
startIndex = 0;
|
||||
}
|
||||
|
@ -9,6 +9,13 @@ import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Pixiv搜索URL构造器
|
||||
* <p>该构造器通过分析Pixiv搜索链接可用的参数而开发, 对搜索链接的构造有高度自定义能力.</p>
|
||||
* @author LamGC
|
||||
* @see PixivURL#PIXIV_SEARCH_CONTENT_URL
|
||||
*/
|
||||
@SuppressWarnings("ALL")
|
||||
public class PixivSearchBuilder {
|
||||
|
||||
private final String content;
|
||||
|
@ -1,171 +0,0 @@
|
||||
package net.lamgc.cgj.pixiv;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.CookieStore;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.BasicCookieStore;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PixivSession {
|
||||
|
||||
public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36";
|
||||
|
||||
/**
|
||||
* 全登陆过程的关键,
|
||||
* 保存会话用的cookieStore!
|
||||
*/
|
||||
private CookieStore cookieStore = new BasicCookieStore();
|
||||
|
||||
/**
|
||||
* 可以直接使用的HttpClient对象
|
||||
*/
|
||||
private HttpClient httpClient;
|
||||
|
||||
/**
|
||||
* 最后一次登录的错误信息
|
||||
*/
|
||||
private String errMsg;
|
||||
|
||||
public PixivSession(){
|
||||
this(null);
|
||||
}
|
||||
|
||||
public PixivSession(CookieStore cookieStore){
|
||||
this(null, cookieStore);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个Pixiv登录会话
|
||||
*/
|
||||
public PixivSession(HttpHost proxy, CookieStore cookieStore) {
|
||||
if(cookieStore != null){
|
||||
this.cookieStore = cookieStore;
|
||||
}
|
||||
List<Header> defaultHeader = new ArrayList<>();
|
||||
defaultHeader.add(new BasicHeader("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"));
|
||||
defaultHeader.add(new BasicHeader("user-agent", PixivSession.USER_AGENT));
|
||||
defaultHeader.add(new BasicHeader("accept-encoding", "gzip, deflate, br"));
|
||||
defaultHeader.add(new BasicHeader("accept-language", "zh-CN,zh;q=0.9"));
|
||||
|
||||
/*defaultHeader.add(new BasicHeader("sec-fetch-mode", "navigate"));
|
||||
defaultHeader.add(new BasicHeader("sec-fetch-site", "same-origin"));
|
||||
defaultHeader.add(new BasicHeader("upgrade-insecure-requests", "1"));*/
|
||||
//创建一个Http访问器
|
||||
httpClient = HttpClients.custom()
|
||||
.setDefaultCookieStore(cookieStore)
|
||||
.setDefaultHeaders(defaultHeader)
|
||||
.setProxy(proxy)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 程序自行通过帐号密码登录Pixiv.
|
||||
* @param PixivID Pixiv帐号
|
||||
* @param Password Pixiv密码
|
||||
* @return 登录成功返回true
|
||||
* @throws IOException 当登录抛出异常时返回
|
||||
* @deprecated Pixiv已经新增Google人机验证, 程序已无法自行登录Pixiv
|
||||
*/
|
||||
public boolean Login(String PixivID, String Password) throws IOException {
|
||||
// 获取登录接口所需的PostKey
|
||||
String post_key = getPostKey();
|
||||
HttpPost postRequest = new HttpPost(PixivURL.PIXIV_LOGIN_URL); //https://accounts.pixiv.net/api/login?lang=zh
|
||||
List<NameValuePair> params = new ArrayList<>();
|
||||
params.add(new BasicNameValuePair("pixiv_id", PixivID));
|
||||
params.add(new BasicNameValuePair("password", Password));
|
||||
params.add(new BasicNameValuePair("post_key", post_key));
|
||||
//Form编码表单,作为Post的数据
|
||||
postRequest.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
|
||||
//启动访问
|
||||
HttpResponse response = httpClient.execute(postRequest);
|
||||
//获取接口返回数据
|
||||
String httpXML = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
System.out.println(httpXML);
|
||||
JsonObject responseJson = (JsonObject) new JsonParser().parse(httpXML);
|
||||
if(!responseJson.get("error").getAsBoolean() && !responseJson.get("body").getAsJsonObject().has("validation_errors")){
|
||||
errMsg = null;
|
||||
return true;
|
||||
}else{
|
||||
errMsg = responseJson.get("body").getAsJsonObject().get("validation_errors").toString();
|
||||
//System.err.println("登录失败!MSG: " + errMsg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录前准备, 获取PostKey
|
||||
* @return Post_Key
|
||||
*/
|
||||
private String getPostKey() throws IOException {
|
||||
//创建请求,获取PostKey
|
||||
HttpGet getRequest = new HttpGet(PixivURL.PIXIV_LOGIN_PAGE_URL);
|
||||
//设置请求
|
||||
//getRequest.setConfig(config);
|
||||
getRequest.setHeader("User-Agent", USER_AGENT);
|
||||
//启动访问
|
||||
HttpResponse response = httpClient.execute(getRequest);
|
||||
//获取网页内容
|
||||
String pageAsXML = EntityUtils.toString(response.getEntity(),"utf-8");
|
||||
//创建Http解析器
|
||||
Document document = Jsoup.parse(pageAsXML);
|
||||
//获取init-config内容
|
||||
String init_config = document.getElementById("init-config").val();
|
||||
//System.out.println(init_config);
|
||||
//创建Json解析器解析init-config
|
||||
JsonObject initConfigObj = (JsonObject) new JsonParser().parse(init_config);
|
||||
//检查是否有postKey
|
||||
if(!initConfigObj.has("pixivAccount.postKey")){
|
||||
throw new RuntimeException("postKey获取失败!可能是Pixiv修改了登录过程!");
|
||||
}
|
||||
//获取postKey
|
||||
return initConfigObj.get("pixivAccount.postKey").getAsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取CookieStore
|
||||
* @return CookieStore
|
||||
*/
|
||||
public CookieStore getCookieStore(){
|
||||
return cookieStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可直接使用的HttpClient对象
|
||||
* @return 已配置好的HttpClient对象
|
||||
*/
|
||||
public HttpClient getHttpClient(){
|
||||
return this.httpClient;
|
||||
}
|
||||
|
||||
public boolean hasError(){
|
||||
return errMsg == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
* @return 返回登录错误信息
|
||||
* @deprecated {@link #Login(String, String)}已经废除, 故本接口废除
|
||||
*/
|
||||
public String getErrMsg(){
|
||||
return errMsg;
|
||||
}
|
||||
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package net.lamgc.cgj.pixiv;
|
||||
|
||||
public class PixivTag {
|
||||
|
||||
public static PixivTag create(String tagName) {
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private PixivTag() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -9,6 +9,7 @@ import java.util.GregorianCalendar;
|
||||
/**
|
||||
* 目前已整理的一些Pixiv接口列表
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class PixivURL {
|
||||
|
||||
|
||||
@ -52,10 +53,10 @@ public class PixivURL {
|
||||
|
||||
/**
|
||||
* P站用户插图列表获取API
|
||||
* <p>所需数据在 body属性内的 illusts(属性名,属性值不重要), manga(多图) pickup(精选)</p>
|
||||
* 需要替换的文本:
|
||||
* {userId} - 用户ID
|
||||
*/
|
||||
//TODO: 所需数据在 body属性内的 illusts(属性名,属性值不重要), manga(多图) pickup(精选)
|
||||
//{"error":false,"message":"","body":{"illusts":{"74369837":null,"70990542":null,"70608653":null,"69755191":null,"69729450":null,"69729416":null,"69503608":null,"69288766":null,"69083882":null,"69051458":null,"68484200":null,"68216927":null,"68216866":null,"68192333":null,"67915106":null,"67914932":null,"67854803":null,"67854745":null,"67854670":null,"67787211":null,"67772199":null,"67770637":null,"67754861":null,"67754804":null,"67754726":null,"67740486":null,"67740480":null,"67740450":null,"67740434":null,"67726337":null,"67499196":null,"67499163":null,"67499145":null,"67499111":null,"67499085":null,"67499038":null,"67498987":null,"67473178":null,"66271465":null,"63682753":null,"63682697":null,"59385148":null,"59383265":null,"59383240":null,"59383227":null,"59383173":null},"manga":[],"novels":[],"mangaSeries":[],"novelSeries":[],"pickup":[],"bookmarkCount":{"public":{"illust":1,"novel":0},"private":{"illust":0,"novel":0}}}}
|
||||
public static final String PIXIV_USER_ILLUST_LIST_URL = "https://www.pixiv.net/ajax/user/{userId}/profile/all";
|
||||
|
||||
|
@ -26,8 +26,7 @@ import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
/**
|
||||
* Pixiv动图构建器.
|
||||
* 可便捷的接收并处理动图.
|
||||
* Pixiv动图构建器
|
||||
*/
|
||||
public final class PixivUgoiraBuilder {
|
||||
|
||||
|
@ -1,31 +0,0 @@
|
||||
package net.lamgc.cgj.util;
|
||||
|
||||
import org.apache.http.client.CookieStore;
|
||||
import org.apache.http.impl.client.BasicCookieStore;
|
||||
import org.apache.http.impl.cookie.BasicClientCookie;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class CookieUtil {
|
||||
|
||||
/**
|
||||
* 将{@link java.net.CookieStore}转换到{@link CookieStore}
|
||||
* @param netCookieStore java.net.CookieStore
|
||||
* @return org.apache.http.client.CookieStore
|
||||
*/
|
||||
public static CookieStore NAParse(java.net.CookieStore netCookieStore){
|
||||
CookieStore apacheCookieStore = new BasicCookieStore();
|
||||
netCookieStore.getCookies().forEach(netCookie -> {
|
||||
BasicClientCookie aCookie = new BasicClientCookie(netCookie.getName(), netCookie.getValue());
|
||||
aCookie.setComment(netCookie.getComment());
|
||||
aCookie.setDomain(netCookie.getDomain());
|
||||
aCookie.setExpiryDate(new Date(netCookie.getMaxAge()));
|
||||
aCookie.setPath(netCookie.getPath());
|
||||
aCookie.setSecure(netCookie.getSecure());
|
||||
aCookie.setVersion(netCookie.getVersion());
|
||||
apacheCookieStore.addCookie(aCookie);
|
||||
});
|
||||
return apacheCookieStore;
|
||||
}
|
||||
|
||||
}
|
@ -7,7 +7,7 @@ import net.lamgc.utils.base.runner.StringParameterParser;
|
||||
public class PagesQualityParser implements StringParameterParser<PixivDownload.PageQuality> {
|
||||
|
||||
@Override
|
||||
public PixivDownload.PageQuality parse(String strValue) throws Exception {
|
||||
public PixivDownload.PageQuality parse(String strValue) {
|
||||
return PixivDownload.PageQuality.valueOf(strValue.toUpperCase());
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,151 @@
|
||||
package net.lamgc.cgj.util;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* 带有时间限制的线程池.
|
||||
* 当线程超出了限制时间时, 将会对该线程发出中断.
|
||||
*/
|
||||
public class TimeLimitThreadPoolExecutor extends ThreadPoolExecutor {
|
||||
|
||||
/**
|
||||
* 执行时间限制, 单位毫秒.
|
||||
* 默认30s.
|
||||
*/
|
||||
private final AtomicLong executeTimeLimit = new AtomicLong();
|
||||
|
||||
/**
|
||||
* 检查间隔时间.
|
||||
* 默认100ms.
|
||||
*/
|
||||
private final AtomicLong timeoutCheckInterval = new AtomicLong(100);
|
||||
|
||||
private final Map<Thread, AtomicLong> workerThreadMap = new Hashtable<>();
|
||||
|
||||
private final Thread timeoutCheckThread = createTimeoutCheckThread();
|
||||
|
||||
public TimeLimitThreadPoolExecutor(long executeLimitTime, int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
|
||||
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
|
||||
setInitialTime(0, executeLimitTime);
|
||||
timeoutCheckThread.start();
|
||||
}
|
||||
|
||||
public TimeLimitThreadPoolExecutor(long executeLimitTime, int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
|
||||
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
|
||||
setInitialTime(0, executeLimitTime);
|
||||
timeoutCheckThread.start();
|
||||
}
|
||||
|
||||
public TimeLimitThreadPoolExecutor(long executeLimitTime, int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
|
||||
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
|
||||
setInitialTime(0, executeLimitTime);
|
||||
timeoutCheckThread.start();
|
||||
}
|
||||
|
||||
public TimeLimitThreadPoolExecutor(long executeLimitTime, int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
|
||||
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
|
||||
setInitialTime(0, executeLimitTime);
|
||||
timeoutCheckThread.start();
|
||||
}
|
||||
|
||||
private void setInitialTime(long checkInterval, long executeLimitTime) {
|
||||
if(checkInterval > 0) {
|
||||
timeoutCheckInterval.set(checkInterval);
|
||||
}
|
||||
if(executeLimitTime > 0) {
|
||||
executeTimeLimit.set(executeLimitTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置执行时间.
|
||||
* <p>注意: 该修改仅在线程池完全停止后才有效</p>
|
||||
* @see #isTerminated()
|
||||
* @param time 新的限制时间(ms)
|
||||
*/
|
||||
public void setExecuteTimeLimit(long time) {
|
||||
if(time <= 0) {
|
||||
throw new IllegalArgumentException("Time is not allowed to be set to 0 or less");
|
||||
}
|
||||
if(this.isTerminated()) {
|
||||
executeTimeLimit.set(time);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置超时检查间隔.
|
||||
* <p>该方法仅会在当前检查后生效.</p>
|
||||
* @param time 新的检查间隔(ms)
|
||||
*/
|
||||
public void setTimeoutCheckInterval(long time) {
|
||||
if(time <= 0) {
|
||||
throw new IllegalArgumentException("Time is not allowed to be set to 0 or less");
|
||||
}
|
||||
timeoutCheckInterval.set(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前设置的执行时间限制.
|
||||
* @return 执行时间限制(ms).
|
||||
*/
|
||||
public long getExecuteTimeLimit() {
|
||||
return executeTimeLimit.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前设定的超时检查间隔
|
||||
* @return 间隔时间(ms).
|
||||
*/
|
||||
public long getTimeoutCheckInterval() {
|
||||
return timeoutCheckInterval.get();
|
||||
}
|
||||
|
||||
private Thread createTimeoutCheckThread() {
|
||||
Thread checkThread = new Thread(() -> {
|
||||
if(executeTimeLimit.get() <= 0) {
|
||||
return;
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
long interval = this.timeoutCheckInterval.get();
|
||||
Thread.sleep(interval);
|
||||
|
||||
// 检查是否存在超时的任务
|
||||
workerThreadMap.forEach((thread, time) -> {
|
||||
long currentTime = time.getAndAdd(interval);
|
||||
if(currentTime > executeTimeLimit.get()) {
|
||||
if(!thread.isInterrupted()) {
|
||||
thread.interrupt();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch(InterruptedException ignored) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
checkThread.setName("ThreadPool-" + Integer.toHexString(this.hashCode()) +"-TimeoutCheck");
|
||||
return checkThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void beforeExecute(Thread t, Runnable r) {
|
||||
workerThreadMap.put(t, new AtomicLong());
|
||||
super.beforeExecute(t, r);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterExecute(Runnable r, Throwable t) {
|
||||
workerThreadMap.remove(Thread.currentThread());
|
||||
super.afterExecute(r, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void terminated() {
|
||||
this.timeoutCheckThread.interrupt();
|
||||
super.terminated();
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
<property name="logStorePath">./logs</property>
|
||||
<property name="charset">UTF-8</property>
|
||||
<property name="pattern">[%-d{HH:mm:ss.SSS} %5level][%logger.%method():%-3L][%thread]: %msg%n</property>
|
||||
<property name="logsDir">${sys:cgj.logsPath:-logs}</property>
|
||||
</properties>
|
||||
|
||||
<Appenders>
|
||||
@ -20,7 +21,7 @@
|
||||
</Filters>
|
||||
</Console>
|
||||
|
||||
<RollingFile name="rollingFile" fileName="logs/latest.log" filePattern="logs/running.%-d{yyyy-MM-dd_HH-mm-ss}.log.gz">
|
||||
<RollingFile name="rollingFile" fileName="${logsDir}/latest.log" filePattern="${logsDir}/running.%-d{yyyy-MM-dd_HH-mm-ss}.log.gz">
|
||||
<PatternLayout pattern="${pattern}" charset="${charset}"/>
|
||||
<Policies>
|
||||
<OnStartupTriggeringPolicy />
|
||||
|
@ -4,6 +4,7 @@
|
||||
<property name="logStorePath">./logs</property>
|
||||
<property name="charset">UTF-8</property>
|
||||
<property name="pattern">[%-d{HH:mm:ss.SSS} %5level][%logger.%method():%-3L][%thread]: %msg%n</property>
|
||||
<property name="logsDir">${sys:cgj.logsPath:-logs}</property>
|
||||
</properties>
|
||||
|
||||
<Appenders>
|
||||
@ -20,7 +21,7 @@
|
||||
</Filters>
|
||||
</Console>
|
||||
|
||||
<RollingFile name="rollingFile" fileName="logs/latest.log" filePattern="logs/running.%-d{yyyy-MM-dd_HH-mm-ss}.log.gz">
|
||||
<RollingFile name="rollingFile" fileName="${logsDir}/latest.log" filePattern="${logsDir}/running.%-d{yyyy-MM-dd_HH-mm-ss}.log.gz">
|
||||
<PatternLayout pattern="${pattern}" charset="${charset}"/>
|
||||
<Policies>
|
||||
<OnStartupTriggeringPolicy />
|
||||
|
@ -181,19 +181,6 @@ public class PixivDownloadTest {
|
||||
log.info("正在调用方法...");
|
||||
try {
|
||||
pixivDownload.getRankingAsInputStream(null, null, queryDate, 5, 50, PixivDownload.PageQuality.ORIGINAL, (rank, link, rankInfo, inputStream) -> {
|
||||
/*try {
|
||||
|
||||
ZipEntry entry = new ZipEntry("Rank" + rank + "-" + link.substring(link.lastIndexOf("/") + 1));
|
||||
entry.setComment(rankInfo.toString());
|
||||
log.info("正在写入: " + entry.getName());
|
||||
zos.putNextEntry(entry);
|
||||
IOUtils.copy(inputStream, zos);
|
||||
zos.flush();
|
||||
log.info("已成功写入 {}", entry.getName());
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
log.error("写入文件项时发生异常", e);
|
||||
}*/
|
||||
log.info("空操作");
|
||||
});
|
||||
} finally {
|
||||
|
@ -0,0 +1,30 @@
|
||||
package net.lamgc.cgj.util;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TimeLimitThreadPoolExecutorTest {
|
||||
|
||||
@Test
|
||||
public void timeoutTest() throws InterruptedException {
|
||||
TimeLimitThreadPoolExecutor executor = new TimeLimitThreadPoolExecutor(1000, 1, 1, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50));
|
||||
System.out.println(executor.isTerminated());
|
||||
System.out.println(executor.isShutdown());
|
||||
|
||||
executor.setTimeoutCheckInterval(150);
|
||||
System.out.println("当前设定: ETL: " + executor.getExecuteTimeLimit() + "ms, TCI: " + executor.getTimeoutCheckInterval() + "ms");
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
Thread.sleep(5 * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("线程 " + Thread.currentThread().getName() + " 被中断");
|
||||
}
|
||||
});
|
||||
executor.shutdown();
|
||||
Assert.assertTrue(executor.awaitTermination(5 * 1000, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user