├── .gitignore ├── com.lyw.hentaibot.json ├── src └── main │ └── java │ └── com │ └── lyw │ ├── config │ ├── LocalConfig.java │ └── SourceConfig.java │ ├── utils │ ├── RandomUtils.java │ ├── HttpUtils.java │ └── LocalUtils.java │ ├── modules │ ├── SaucenaoModule.java │ └── YandereModule.java │ ├── HentaiAppAbstract.java │ └── Hentaibot.java ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | target 4 | -------------------------------------------------------------------------------- /com.lyw.hentaibot.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lywbh/hentai-bot/HEAD/com.lyw.hentaibot.json -------------------------------------------------------------------------------- /src/main/java/com/lyw/config/LocalConfig.java: -------------------------------------------------------------------------------- 1 | package com.lyw.config; 2 | 3 | public class LocalConfig { 4 | 5 | public static final String IMAGE_PATH = "data/image/"; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/lyw/config/SourceConfig.java: -------------------------------------------------------------------------------- 1 | package com.lyw.config; 2 | 3 | public class SourceConfig { 4 | 5 | public static final String SOURCE_YANDERE = "http://yande.re/post.json"; 6 | 7 | public static final String SOURCE_KONACHAN = "http://konachan.net/post.json"; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/lyw/utils/RandomUtils.java: -------------------------------------------------------------------------------- 1 | package com.lyw.utils; 2 | 3 | import java.util.Random; 4 | 5 | public class RandomUtils { 6 | 7 | private static Random random = new Random(); 8 | 9 | private static Random getRandom() { 10 | return random; 11 | } 12 | 13 | /** 14 | * 获得一个[0,max)之间的整数。 15 | */ 16 | public static int getRandomInt(int max) { 17 | return getRandomInt(0, max); 18 | } 19 | 20 | /** 21 | * 获得一个[min,max)之间的整数。 22 | */ 23 | public static int getRandomInt(int min, int max) { 24 | return Math.abs(getRandom().nextInt()) % max + min; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hentai-bot -- QQ色图机器人 2 | 3 | ##### [yande.re](http://yande.re) / [konachan](http://konachan.net),图源待增加 4 | 5 | ![sample](https://puu.sh/Ea7tG/ae06bece70.png) 6 | 7 | ##### 按照图站的分类,有safe、questionable、explicit三个级别 8 | 9 | ```text 10 | !色图 s 11 | !色图 q 12 | !色图 e 13 | ``` 14 | 15 | ##### 不指定则默认为s 16 | 17 | ##### 可以反向搜索出处 18 | 19 | ```text 20 | !车来 [图片] 21 | ``` 22 | 23 | ![sample](https://puu.sh/EdA3V/6b2e041f27.png) 24 | 25 | ##### 这里直接接了[saucenao](http://saucenao.com/) 26 | 27 | ## Installation 28 | 29 | 1、32位 [JDK(JRE)](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) 1.6以上版本 30 | 31 | 2、按照 [这里](https://cqp.cc/t/37318) 的指引安装java sdk 32 | 33 | 3、自己编译打包或者到 [release](https://github.com/lywbh/hentai-bot/releases) 下载插件,解压于"/酷Q Pro/app/com.sobte.cqp.jcq"目录下 34 | 35 | 4、重启酷Q并到插件管理页启动即可 36 | 37 | 38 | ###### 一直冲一直爽.jpg 39 | -------------------------------------------------------------------------------- /src/main/java/com/lyw/utils/HttpUtils.java: -------------------------------------------------------------------------------- 1 | package com.lyw.utils; 2 | 3 | import com.sobte.cqp.jcq.event.JcqApp; 4 | import org.apache.http.client.methods.CloseableHttpResponse; 5 | import org.apache.http.client.methods.HttpGet; 6 | import org.apache.http.impl.client.CloseableHttpClient; 7 | import org.apache.http.impl.client.HttpClientBuilder; 8 | import org.apache.http.util.EntityUtils; 9 | 10 | import java.io.IOException; 11 | 12 | public class HttpUtils { 13 | 14 | private static final CloseableHttpClient httpClient = HttpClientBuilder.create().build(); 15 | 16 | public static String get(String url) { 17 | CloseableHttpResponse httpResponse = null; 18 | try { 19 | httpResponse = httpClient.execute(new HttpGet(url)); 20 | return EntityUtils.toString(httpResponse.getEntity()); 21 | } catch (IOException e) { 22 | JcqApp.CQ.logError("hentai-bot", e.getMessage()); 23 | return null; 24 | } finally { 25 | if (httpResponse != null) { 26 | try { 27 | httpResponse.close(); 28 | } catch (IOException e) { 29 | JcqApp.CQ.logError("hentai-bot", e.getMessage()); 30 | } 31 | } 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/lyw/utils/LocalUtils.java: -------------------------------------------------------------------------------- 1 | package com.lyw.utils; 2 | 3 | import com.lyw.config.LocalConfig; 4 | import com.sobte.cqp.jcq.event.JcqApp; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.File; 8 | import java.io.FileReader; 9 | import java.io.IOException; 10 | 11 | public class LocalUtils { 12 | 13 | public static String findImgUrl(String imageName) { 14 | if (imageName == null || imageName.isEmpty()) { 15 | return null; 16 | } 17 | FileReader fr = null; 18 | BufferedReader bf = null; 19 | try { 20 | File imgFile = new File(LocalConfig.IMAGE_PATH + imageName + ".cqimg"); 21 | fr = new FileReader(imgFile); 22 | bf = new BufferedReader(fr); 23 | String line; 24 | while ((line = bf.readLine()) != null) { 25 | if (line.startsWith("url=")) { 26 | return line.substring(4); 27 | } 28 | } 29 | return null; 30 | } catch (IOException e) { 31 | JcqApp.CQ.logError("hentai-bot", e.getMessage()); 32 | return null; 33 | } finally { 34 | try { 35 | if (bf != null) { 36 | bf.close(); 37 | } 38 | if (fr != null) { 39 | fr.close(); 40 | } 41 | } catch (IOException e) { 42 | JcqApp.CQ.logError("hentai-bot", e.getMessage()); 43 | } 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/lyw/modules/SaucenaoModule.java: -------------------------------------------------------------------------------- 1 | package com.lyw.modules; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONObject; 6 | import com.lyw.utils.HttpUtils; 7 | import com.sobte.cqp.jcq.event.JcqApp; 8 | 9 | import java.io.*; 10 | import java.net.URLEncoder; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class SaucenaoModule { 15 | 16 | private static final String baseUrl = "https://saucenao.com/search.php"; 17 | 18 | public Map bestMatch(String imgUrl) { 19 | Map result = new HashMap<>(); 20 | if (imgUrl == null || imgUrl.isEmpty()) { 21 | return result; 22 | } 23 | String url = baseUrl; 24 | try { 25 | url += "?db=999&output_type=2&testmode=1&url=" + URLEncoder.encode(imgUrl, "UTF-8"); 26 | } catch (UnsupportedEncodingException e) { 27 | JcqApp.CQ.logError("hentai-bot", e.getMessage()); 28 | return result; 29 | } 30 | String response = HttpUtils.get(url); 31 | JSONArray respJson = JSON.parseObject(response).getJSONArray("results"); 32 | if (respJson.isEmpty()) { 33 | return result; 34 | } 35 | JSONObject dataJson = respJson.getJSONObject(0).getJSONObject("data"); 36 | dataJson.forEach((k, v) -> { 37 | if (v != null) { 38 | String value; 39 | if (v instanceof JSON) { 40 | value = ((JSON) v).toJSONString(); 41 | } else { 42 | value = v.toString(); 43 | } 44 | result.put(k, value); 45 | } 46 | }); 47 | return result; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/lyw/HentaiAppAbstract.java: -------------------------------------------------------------------------------- 1 | package com.lyw; 2 | 3 | import com.sobte.cqp.jcq.entity.ICQVer; 4 | import com.sobte.cqp.jcq.entity.IMsg; 5 | import com.sobte.cqp.jcq.event.JcqAppAbstract; 6 | 7 | public abstract class HentaiAppAbstract extends JcqAppAbstract { 8 | 9 | private static final String APP_ID = "com.lyw.hentaibot"; 10 | 11 | public String appInfo() { 12 | return ICQVer.CQAPIVER + "," + APP_ID; 13 | } 14 | 15 | public int exit() { 16 | return 0; 17 | } 18 | 19 | public int enable() { 20 | enable = true; 21 | return 0; 22 | } 23 | 24 | public int disable() { 25 | enable = false; 26 | return 0; 27 | } 28 | 29 | public int privateMsg(int subType, int msgId, long fromQQ, String msg, int font) { 30 | return IMsg.MSG_IGNORE; 31 | } 32 | 33 | public int discussMsg(int subtype, int msgId, long fromDiscuss, long fromQQ, String msg, int font) { 34 | return IMsg.MSG_IGNORE; 35 | } 36 | 37 | public int groupUpload(int subType, int sendTime, long fromGroup, long fromQQ, String file) { 38 | return IMsg.MSG_IGNORE; 39 | } 40 | 41 | public int groupAdmin(int subtype, int sendTime, long fromGroup, long beingOperateQQ) { 42 | return IMsg.MSG_IGNORE; 43 | } 44 | 45 | public int groupMemberDecrease(int subtype, int sendTime, long fromGroup, long fromQQ, long beingOperateQQ) { 46 | return IMsg.MSG_IGNORE; 47 | } 48 | 49 | public int groupMemberIncrease(int subtype, int sendTime, long fromGroup, long fromQQ, long beingOperateQQ) { 50 | return IMsg.MSG_IGNORE; 51 | } 52 | 53 | public int friendAdd(int subtype, int sendTime, long fromQQ) { 54 | return IMsg.MSG_IGNORE; 55 | } 56 | 57 | public int requestAddFriend(int subtype, int sendTime, long fromQQ, String msg, String responseFlag) { 58 | return IMsg.MSG_IGNORE; 59 | } 60 | 61 | public int requestAddGroup(int subtype, int sendTime, long fromGroup, long fromQQ, String msg, String responseFlag) { 62 | return IMsg.MSG_IGNORE; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/lyw/modules/YandereModule.java: -------------------------------------------------------------------------------- 1 | package com.lyw.modules; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONObject; 6 | import com.lyw.config.SourceConfig; 7 | import com.lyw.utils.HttpUtils; 8 | import com.lyw.utils.RandomUtils; 9 | import com.sobte.cqp.jcq.entity.CQImage; 10 | import com.sobte.cqp.jcq.event.JcqApp; 11 | 12 | import java.io.IOException; 13 | 14 | public class YandereModule { 15 | 16 | public enum Rating { 17 | UNKNOWN, SAFE, QUESTIONABLE, EXPLICIT 18 | } 19 | 20 | public Rating rating(String rating) { 21 | rating = rating.trim(); 22 | switch (rating) { 23 | case "s": 24 | case "safe": 25 | return Rating.SAFE; 26 | case "q": 27 | case "questionable": 28 | return Rating.QUESTIONABLE; 29 | case "e": 30 | case "explicit": 31 | return Rating.EXPLICIT; 32 | default: 33 | return Rating.UNKNOWN; 34 | } 35 | } 36 | 37 | public CQImage randomPic() { 38 | return randomPic(Rating.SAFE, null); 39 | } 40 | 41 | public CQImage randomPic(Rating rating) { 42 | return randomPic(rating, null); 43 | } 44 | 45 | public CQImage randomPic(String tags) { 46 | return randomPic(Rating.SAFE, tags); 47 | } 48 | 49 | public CQImage randomPic(Rating rating, String tags) { 50 | return randomPic(rating, tags, 100); 51 | } 52 | 53 | private CQImage randomPic(Rating rating, String tags, int bound) { 54 | if (bound == 0) { 55 | return null; 56 | } 57 | if (rating == Rating.UNKNOWN) { 58 | rating = Rating.SAFE; 59 | } 60 | if (tags == null) { 61 | tags = ""; 62 | } 63 | int page = RandomUtils.getRandomInt(bound) + 1; 64 | String url = SourceConfig.SOURCE_KONACHAN + "?limit=100&page=" + page + "&tags=" + tags + "%20rating:" + rating.name().toLowerCase(); 65 | String response = HttpUtils.get(url); 66 | if (response == null) { 67 | return null; 68 | } 69 | JSONArray respArray = JSON.parseArray(response); 70 | if (respArray.isEmpty()) { 71 | return randomPic(rating, tags, bound / 2); 72 | } 73 | int rc = RandomUtils.getRandomInt(respArray.size()); 74 | JSONObject respJson = respArray.getJSONObject(rc); 75 | try { 76 | return new CQImage(respJson.getString("sample_url")); 77 | } catch (IOException e) { 78 | JcqApp.CQ.logError("hentai-bot", e.getMessage()); 79 | return null; 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.lyw 8 | hentaibot 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | UTF-8 13 | 14 | 15 | 16 | 17 | com.sobte.cqp 18 | jcq-coolq 19 | 1.2.7 20 | 21 | 22 | org.apache.httpcomponents 23 | httpclient 24 | 4.5.5 25 | 26 | 27 | com.alibaba 28 | fastjson 29 | 1.2.46 30 | 31 | 32 | 33 | 34 | 35 | ${project.groupId}.${project.artifactId} 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-dependency-plugin 40 | 41 | 42 | copy-dependencies 43 | prepare-package 44 | 45 | copy-dependencies 46 | 47 | 48 | ${project.build.directory}/lib 49 | false 50 | false 51 | true 52 | 53 | 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-compiler-plugin 59 | 3.8.1 60 | 61 | 8 62 | 8 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/main/java/com/lyw/Hentaibot.java: -------------------------------------------------------------------------------- 1 | package com.lyw; 2 | 3 | import com.lyw.modules.SaucenaoModule; 4 | import com.lyw.modules.YandereModule; 5 | import com.lyw.utils.LocalUtils; 6 | import com.sobte.cqp.jcq.entity.CQDebug; 7 | import com.sobte.cqp.jcq.entity.CQImage; 8 | import com.sobte.cqp.jcq.entity.IMsg; 9 | import com.sobte.cqp.jcq.event.JcqApp; 10 | import com.sobte.cqp.jcq.message.CQCode; 11 | 12 | import java.io.IOException; 13 | import java.util.Map; 14 | 15 | public class Hentaibot extends HentaiAppAbstract { 16 | 17 | private YandereModule yandereModule; 18 | 19 | private SaucenaoModule saucenaoModule; 20 | 21 | public int startup() { 22 | CQ.logInfo("hentai-bot", "初始化图库插件..."); 23 | yandereModule = new YandereModule(); 24 | saucenaoModule = new SaucenaoModule(); 25 | CQ.logInfo("hentai-bot", "初始化完毕"); 26 | return 0; 27 | } 28 | 29 | public int groupMsg(int subType, int msgId, long fromGroup, long fromQQ, String fromAnonymous, String msg, int font) { 30 | if (yandereModule == null || saucenaoModule == null) { 31 | return IMsg.MSG_IGNORE; 32 | } 33 | if (msg.startsWith("!色图")) { 34 | String[] splitMsg = msg.split(" "); 35 | CQImage image; 36 | if (splitMsg.length == 1) { 37 | image = yandereModule.randomPic(); 38 | } else if (splitMsg.length == 2) { 39 | YandereModule.Rating rating = yandereModule.rating(splitMsg[1]); 40 | if (rating == YandereModule.Rating.UNKNOWN) { 41 | image = yandereModule.randomPic(splitMsg[1]); 42 | } else { 43 | image = yandereModule.randomPic(rating); 44 | } 45 | } else if (splitMsg.length == 3) { 46 | YandereModule.Rating rating = yandereModule.rating(splitMsg[1]); 47 | image = yandereModule.randomPic(rating, splitMsg[2]); 48 | } else { 49 | JcqApp.CQ.sendGroupMsg(fromGroup, "指令格式有误,请确认没有多余的空格"); 50 | return IMsg.MSG_IGNORE; 51 | } 52 | if (image != null) { 53 | try { 54 | JcqApp.CQ.sendGroupMsg(fromGroup, new CQCode().image(image)); 55 | } catch (IOException e) { 56 | JcqApp.CQ.logError("hentai-bot", e.getMessage()); 57 | } 58 | } 59 | } else if (msg.startsWith("!车来")) { 60 | String imageName = new CQCode().getImage(msg.substring(3)); 61 | String imgUrl = LocalUtils.findImgUrl(imageName); 62 | Map bestMatch = saucenaoModule.bestMatch(imgUrl); 63 | if (!bestMatch.isEmpty()) { 64 | StringBuilder sb = new StringBuilder(); 65 | bestMatch.forEach((k, v) -> sb.append(k).append(" --> ").append(v).append("\n")); 66 | JcqApp.CQ.sendGroupMsg(fromGroup, sb.toString()); 67 | } else { 68 | JcqApp.CQ.sendGroupMsg(fromGroup, "无匹配源"); 69 | } 70 | } 71 | return IMsg.MSG_IGNORE; 72 | } 73 | 74 | public static void main(String[] args) { 75 | CQ = new CQDebug(); 76 | Hentaibot demo = new Hentaibot(); 77 | demo.startup(); 78 | demo.enable(); 79 | demo.groupMsg(0, 10006, 3456789012L, 3333333334L, "", "!色图 q", 0); 80 | demo.exit(); 81 | } 82 | 83 | } 84 | --------------------------------------------------------------------------------