mirror of
https://github.com/LamGC/ContentGrabbingJi.git
synced 2025-07-02 21:37:26 +00:00
Compare commits
16 Commits
v2.5.2-202
...
v2.5.2-202
Author | SHA1 | Date | |
---|---|---|---|
1e88ba70dd | |||
e6b2544998 | |||
a2f6f1d140 | |||
f54ed35a09 | |||
a426f80ec5 | |||
c1a21d1065 | |||
e570ddbb53 | |||
223d78dbd6 | |||
ef5651be47 | |||
9a8aac1960 | |||
4bbed5fd55 | |||
bcc21149b9 | |||
e93c322c02 | |||
feb51b8534 | |||
273dbd45b0 | |||
b39a82b936 |
108
documents/interfaces/Pixiv作品信息获取接口.md
Normal file
108
documents/interfaces/Pixiv作品信息获取接口.md
Normal file
@ -0,0 +1,108 @@
|
||||
## Pixiv作品信息批量获取接口 ##
|
||||
### 说明 ###
|
||||
接口可一次获取多个作品的基础信息
|
||||
|
||||
|
||||
### 接口地址 ###
|
||||
```
|
||||
GET https://www.pixiv.net/ajax/illust/recommend/illusts
|
||||
```
|
||||
|
||||
- 需要登录: `否`
|
||||
- 是否为Pixiv接口标准返回格式: `是`
|
||||
### 参数 ###
|
||||
- `illust_ids[]`: 作品Id, 可重复添加该参数
|
||||
|
||||
|
||||
### 请求示例 ###
|
||||
```
|
||||
GET https://www.pixiv.net/ajax/illust/recommend/illusts?illust_ids[]=82030844&illust_ids[]=82029098&illust_ids[]=82028913
|
||||
```
|
||||
|
||||
### 返回数据 ###
|
||||
#### 数据示例 ####
|
||||
```json
|
||||
{
|
||||
"error":false,
|
||||
"message":"",
|
||||
"body":{
|
||||
"illusts":[
|
||||
{
|
||||
"illustId":"82030844",
|
||||
"illustTitle":"3rd anniversary",
|
||||
"id":"82030844",
|
||||
"title":"3rd anniversary",
|
||||
"illustType":0,
|
||||
"xRestrict":0,
|
||||
"restrict":0,
|
||||
"sl":2,
|
||||
"url":"https:\/\/i.pximg.net\/c\/360x360_70\/custom-thumb\/img\/2020\/06\/02\/11\/24\/49\/82030844_p0_custom1200.jpg",
|
||||
"description":"",
|
||||
"tags":[
|
||||
"\u30a2\u30ba\u30fc\u30eb\u30ec\u30fc\u30f3",
|
||||
"\u6bd4\u53e1(\u30a2\u30ba\u30fc\u30eb\u30ec\u30fc\u30f3)",
|
||||
"\u6c34\u7740",
|
||||
"\u8db3\u88cf",
|
||||
"\u88f8\u8db3",
|
||||
"\u30a2\u30ba\u30fc\u30eb\u30ec\u30fc\u30f35000users\u5165\u308a",
|
||||
"\u30a2\u30ba\u30fc\u30eb\u30ec\u30fc\u30f310000users\u5165\u308a",
|
||||
"\u30dd\u30cb\u30fc\u30c6\u30fc\u30eb",
|
||||
"\u30db\u30eb\u30bf\u30fc\u30cd\u30c3\u30af"],
|
||||
"userId":"6662895",
|
||||
"userName":"ATDAN-",
|
||||
"width":1500,
|
||||
"height":844,
|
||||
"pageCount":1,
|
||||
"isBookmarkable":true,
|
||||
"bookmarkData":null,
|
||||
"alt":"#\u30a2\u30ba\u30fc\u30eb\u30ec\u30fc\u30f3 3rd anniversary - ATDAN-\u7684\u63d2\u753b",
|
||||
"isAdContainer":false,
|
||||
"titleCaptionTranslation":{
|
||||
"workTitle":null,
|
||||
"workCaption":null
|
||||
},
|
||||
"createDate":"2020-06-02T01:29:40+09:00",
|
||||
"updateDate":"2020-06-02T11:24:49+09:00",
|
||||
"profileImageUrl":"https:\/\/i.pximg.net\/user-profile\/img\/2016\/01\/11\/21\/46\/50\/10371466_80f6ad67eab3b8abd44a2fb74ddd1ba1_50.jpg",
|
||||
"type":"illust"
|
||||
}, // ...
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
#### 参数详解 ####
|
||||
- `illusts`: (`Object[]`) 存储查询作品信息的数组
|
||||
- `illustId`: (`string` -> `number`) 作品Id
|
||||
- `illustTitle`: (`string`) 作品标题
|
||||
- `id`: (`string` -> `number`) 与`illustId`一致, 猜测是以兼容旧版本为目录而存在
|
||||
- `title`: (`string`) 与`illustTitle`一致, 猜测是以兼容旧版本为目录而存在
|
||||
- `illustType`: (`number`) 作品类型
|
||||
- `0`: 插画作品
|
||||
- `1`: 漫画作品
|
||||
- `2`: 动图作品
|
||||
- `xRestrict`: (`number`) 作品是否为限制级, 基本准确, 少部分不一定(看Pixiv审核怎么理解了)
|
||||
- `0`: 非限制级内容(即非R18作品)
|
||||
- `1`: 限制级内容(即R18作品)
|
||||
- `restrict`: (`number`) 作品限制级(意义不明, 可能是兼容性问题?)?
|
||||
- `sl`: (`number`) 不明?
|
||||
- `url`: (`string`) 作品预览图链接, 需要`Referer`请求头
|
||||
- `description`: (`string`) 作品说明
|
||||
- `tags`: (`string[]`) 作品标签数组
|
||||
- `userId`: (`string` -> `number`) 作者用户Id
|
||||
- `userName`: (`string`) 作者用户名
|
||||
- `width`: (`number`) 作品长度
|
||||
- `height`: (`number`) 作品高度
|
||||
- `pageCount`: (`number`) 作品页数
|
||||
- `isBookmarkable`: (`boolean`) 不明?
|
||||
- `alt`: (`string`) 简略介绍信息(在图片加载失败时可提供给`img`标签使用)
|
||||
- `isAdContainer`: (`boolean`) 不明?
|
||||
- `titleCaptionTranslation`: (`Object`) 不明?
|
||||
- `workTitle`: (`Unknown`) 不明?
|
||||
- `workCaption`: (`Unknown`) 不明?
|
||||
- `createDate`: (`string`) 作品创建时间(或者是完成时间?)
|
||||
- `updateDate`: (`string`) 作品上传时间
|
||||
- `profileImageUrl`: (`string`) 作者用户头像图片链接
|
||||
- `type`: (`string`) 作品类型名
|
||||
|
||||
|
@ -5,7 +5,9 @@
|
||||
GET https://www.pixiv.net/ranking.php
|
||||
```
|
||||
|
||||
- 需要登录: `是`
|
||||
- 是否需要登录: `是`
|
||||
- 是否为Pixiv标准接口返回格式: `否`
|
||||
- 是否需要Referer请求头: `否`
|
||||
### 参数 ###
|
||||
> 提示: 该接口参数较为复杂,请结合表格查看
|
||||
|
||||
@ -113,7 +115,7 @@ female_r18|`√`|×|×|×
|
||||
}
|
||||
```
|
||||
|
||||
#### 参数详解 ####
|
||||
#### 字段说明 ####
|
||||
- `contents`: (`Object[]`) 排行榜数组, 最多50行排行榜信息
|
||||
- `illust_id`: (`number`) 作品Id
|
||||
- `title`: (`string`) 作品标题
|
||||
@ -126,9 +128,24 @@ female_r18|`√`|×|×|×
|
||||
- `user_name`: (`string`) 画师用户名
|
||||
- `user_id`: (`number`) 画师用户Id
|
||||
- `profile_img`: (`string`) 画师用户头像
|
||||
- `illust_content_type`: (`Object`) 作品内容信息
|
||||
- 待补充
|
||||
- `illust_series`: (`boolean`) 不明?
|
||||
- `illust_content_type`: (`Object`) 作品内容信息(警告: 文档内容仅作为开发参考, 并不传播相关内容!!!)
|
||||
- `sexual`: (`number`) 作品内容分级
|
||||
- `0`: 全年龄
|
||||
- `1`: 青少年
|
||||
- `2`: 成人级
|
||||
- `lo`: (`boolean`) 是否为loli作品
|
||||
- `grotesque`: (`boolean`) 是否为怪诞作品
|
||||
- `violent`: (`boolean`) 作品是否含有暴力/强暴相关元素
|
||||
- `homosexual`: (`boolean`) 作品是否含有同性恋相关元素
|
||||
- `drug`: (`boolean`) 作品是否含有药物相关元素
|
||||
- `thoughts`: (`boolean`) 作品是否含有思维/记忆相关元素(这个属性翻译起来有些问题, 待纠正)?
|
||||
- `antisocial`: (`boolean`) 作品是否含有反社会, 令人厌恶的相关元素
|
||||
- `religion`: (`boolean`) 作品是否含有宗教, 信仰相关元素
|
||||
- `original`: (`boolean`) 作品是否为原创作品
|
||||
- `furry`: (`boolean`) 作品是否有兽人相关元素
|
||||
- `bl`: (`boolean`) 作品是否有耽美相关元素
|
||||
- `yuri`: (`boolean`) 作品是否有百合相关元素
|
||||
- `illust_series`: (`boolean`) 是否为系列作品
|
||||
- `width`: (`number`) 作品宽度(建议以原图为准)
|
||||
- `height`: (`number`) 作品高度(建议以原图为准)
|
||||
- `rank`: (`number`) 本期排行榜排名
|
||||
|
32
documents/interfaces/Pixiv接口文档模板.md
Normal file
32
documents/interfaces/Pixiv接口文档模板.md
Normal file
@ -0,0 +1,32 @@
|
||||
## 接口名 ##
|
||||
### 说明 ###
|
||||
|
||||
### 接口地址 ###
|
||||
```
|
||||
GET/POST https://www.pixiv.net/...
|
||||
```
|
||||
|
||||
- 是否需要登录: `是/否`
|
||||
- 是否为Pixiv标准接口返回格式: `是/否`
|
||||
- 是否需要Referer请求头: `是/否`
|
||||
|
||||
### 参数 ###
|
||||
|
||||
### 请求示例 ###
|
||||
```
|
||||
GET/POST
|
||||
---- Request Body ---- // 如果没有, 可以不写, 如没有记得删除
|
||||
|
||||
```
|
||||
|
||||
### 返回数据 ###
|
||||
#### 数据示例 ####
|
||||
```json
|
||||
// Object[] 中只留一个, 其他删除后保留逗号, 增加 '// ...' 注释
|
||||
|
||||
```
|
||||
#### 字段说明 ####
|
||||
- `属性名`: (`JS类型`) 属性说明
|
||||
- `对象属性名`: (`原始JS类型` -> `可转换JS类型`) 属性说明
|
||||
- `属性名`: (`JS类型1` / `JS类型2` / ...) 属性说明, 需要清晰说明在什么情况下类型为`JS类型1`, 什么情况下是`JS类型2`.
|
||||
- `不明属性`: (`Unknown`) 如果属性用途不明, 则在说明后面加上`?`符号, 类型不明则填`Unknown`.
|
@ -9,9 +9,12 @@
|
||||
GET https://www.pixiv.net/ajax/search/{Type}/{Content}?{Param...}
|
||||
```
|
||||
|
||||
- 需要登录: `是`
|
||||
- 是否需要登录: `是/否`
|
||||
- 是否为Pixiv标准接口返回格式: `是/否`
|
||||
- 是否需要Referer请求头: `否`
|
||||
|
||||
### Url参数 ###
|
||||
### 参数 ###
|
||||
#### Url参数 ####
|
||||
- `Type`: 内容类型
|
||||
- illustrations(插画)
|
||||
- top(推荐)
|
||||
@ -19,8 +22,8 @@ GET https://www.pixiv.net/ajax/search/{Type}/{Content}?{Param...}
|
||||
- novels(小说)
|
||||
- `Content`: 搜索内容
|
||||
|
||||
### 参数 ###
|
||||
#### 必填 ####
|
||||
#### GET参数 ####
|
||||
##### 必填 #####
|
||||
- `word`: 与搜索内容一致 (经测试似乎可以省略)
|
||||
- `s_mode`: 匹配模式
|
||||
- `s_tag`: 标签,部分一致
|
||||
@ -42,7 +45,7 @@ GET https://www.pixiv.net/ajax/search/{Type}/{Content}?{Param...}
|
||||
- `safe`: 排除成人内容
|
||||
- `r18`: 仅成人内容
|
||||
|
||||
#### 选填 ####
|
||||
##### 选填 #####
|
||||
- `wlt`: 作品最低宽度(px)
|
||||
- `wgt`: 作品最高宽度(px)
|
||||
- `hlt`: 作品最低高度(px)
|
||||
@ -56,3 +59,307 @@ GET https://www.pixiv.net/ajax/search/{Type}/{Content}?{Param...}
|
||||
- `scd`: 过滤作品发布时间 - 结束时间(yyyy-MM-dd)
|
||||
- `(Unknown)`: 最小收藏数 (该参数为会员限定功能,后续补充)
|
||||
|
||||
### 返回数据 ###
|
||||
#### 数据示例 ####
|
||||
```json
|
||||
{
|
||||
"error":false,
|
||||
"body":{
|
||||
"illustManga":{
|
||||
"data":[
|
||||
{
|
||||
"illustId":"82130571",
|
||||
"illustTitle":"空の絵",
|
||||
"id":"82130571",
|
||||
"title":"空の絵",
|
||||
"illustType":0,
|
||||
"xRestrict":0,
|
||||
"restrict":0,
|
||||
"sl":2,
|
||||
"url":"https:\/\/i.pximg.net\/c\/250x250_80_a2\/img-master\/img\/2020\/06\/06\/17\/51\/14\/82130571_p0_square1200.jpg",
|
||||
"description":"",
|
||||
"tags":[
|
||||
"風景",
|
||||
"空",
|
||||
"草",
|
||||
"雲"],
|
||||
"userId":"31507675",
|
||||
"userName":"昏omeme",
|
||||
"width":1600,
|
||||
"height":1600,
|
||||
"pageCount":2,
|
||||
"isBookmarkable":true,
|
||||
"bookmarkData":null,
|
||||
"alt":"#風景 空の絵 - 昏omeme的插画",
|
||||
"isAdContainer":false,
|
||||
"titleCaptionTranslation":{
|
||||
"workTitle":null,
|
||||
"workCaption":null
|
||||
},
|
||||
"createDate":"2020-06-06T17:51:14+09:00",
|
||||
"updateDate":"2020-06-06T17:51:14+09:00",
|
||||
"profileImageUrl":"https:\/\/i.pximg.net\/user-profile\/img\/2020\/05\/06\/19\/21\/04\/18509741_e3166e69809c44d6926454ecaac89590_50.png"
|
||||
}, // ...
|
||||
],
|
||||
"total":165875,
|
||||
"bookmarkRanges":[
|
||||
{
|
||||
"min":null,
|
||||
"max":null
|
||||
},
|
||||
{
|
||||
"min":10000,
|
||||
"max":null
|
||||
},
|
||||
{
|
||||
"min":5000,
|
||||
"max":null
|
||||
},
|
||||
{
|
||||
"min":1000,
|
||||
"max":null
|
||||
},
|
||||
{
|
||||
"min":500,
|
||||
"max":null
|
||||
},
|
||||
{
|
||||
"min":300,
|
||||
"max":null
|
||||
},
|
||||
{
|
||||
"min":100,
|
||||
"max":null
|
||||
},
|
||||
{
|
||||
"min":50,
|
||||
"max":null
|
||||
}
|
||||
]
|
||||
},
|
||||
"popular":{
|
||||
"recent":[
|
||||
{
|
||||
"illustId":"82062770",
|
||||
"illustTitle":"Still you remember",
|
||||
"id":"82062770",
|
||||
"title":"Still you remember",
|
||||
"illustType":0,
|
||||
"xRestrict":0,
|
||||
"restrict":0,
|
||||
"sl":2,
|
||||
"url":"https:\/\/i.pximg.net\/c\/250x250_80_a2\/img-master\/img\/2020\/06\/03\/18\/02\/15\/82062770_p0_square1200.jpg",
|
||||
"description":"",
|
||||
"tags":[
|
||||
"オリジナル",
|
||||
"女の子",
|
||||
"カラス",
|
||||
"风景",
|
||||
"線路"],
|
||||
"userId":"1069005",
|
||||
"userName":"へちま",
|
||||
"width":2000,
|
||||
"height":1415,
|
||||
"pageCount":1,
|
||||
"isBookmarkable":true,
|
||||
"bookmarkData":null,
|
||||
"alt":"#オリジナル Still you remember - へちま的插画",
|
||||
"isAdContainer":false,
|
||||
"titleCaptionTranslation":{
|
||||
"workTitle":null,
|
||||
"workCaption":null
|
||||
},
|
||||
"createDate":"2020-06-03T18:02:15+09:00",
|
||||
"updateDate":"2020-06-03T18:02:15+09:00",
|
||||
"profileImageUrl":"https:\/\/i.pximg.net\/user-profile\/img\/2013\/05\/10\/00\/38\/05\/6213543_c94edc0d13776214467bd0c47ee6491a_50.jpg"
|
||||
}, // ...
|
||||
],
|
||||
"permanent":[
|
||||
{
|
||||
"illustId":"60993044",
|
||||
"illustTitle":"無題",
|
||||
"id":"60993044",
|
||||
"title":"無題",
|
||||
"illustType":0,
|
||||
"xRestrict":0,
|
||||
"restrict":0,
|
||||
"sl":2,
|
||||
"url":"https:\/\/i.pximg.net\/c\/250x250_80_a2\/img-master\/img\/2017\/01\/18\/13\/07\/46\/60993044_p0_square1200.jpg",
|
||||
"description":"",
|
||||
"tags":[
|
||||
"少女",
|
||||
"女の子",
|
||||
"原创",
|
||||
"オリジナル",
|
||||
"场景",
|
||||
"落書き",
|
||||
"創作",
|
||||
"背景",
|
||||
"风景",
|
||||
"オリジナル7500users入り"],
|
||||
"userId":"18811972",
|
||||
"userName":"淅洵",
|
||||
"width":3507,
|
||||
"height":2480,
|
||||
"pageCount":1,
|
||||
"isBookmarkable":true,
|
||||
"bookmarkData":null,
|
||||
"alt":"#少女 無題 - 淅洵的插画",
|
||||
"isAdContainer":false,
|
||||
"titleCaptionTranslation":{
|
||||
"workTitle":null,
|
||||
"workCaption":null
|
||||
},
|
||||
"createDate":"2017-01-18T13:07:46+09:00",
|
||||
"updateDate":"2017-01-18T13:07:46+09:00",
|
||||
"profileImageUrl":"https:\/\/i.pximg.net\/user-profile\/img\/2017\/05\/29\/17\/17\/49\/12623968_6cf3f1979e10643425972ae205a7920d_50.jpg"
|
||||
}, // ...
|
||||
]
|
||||
},
|
||||
"relatedTags":[
|
||||
"風景",
|
||||
"背景",
|
||||
"風景画",
|
||||
"空",
|
||||
"雲",
|
||||
"創作",
|
||||
"ファンタジー",
|
||||
"夏",
|
||||
"青",
|
||||
"建物",
|
||||
"青空",
|
||||
"少女",
|
||||
"東京",
|
||||
"抽象画",
|
||||
"男の子",
|
||||
"透明水彩"
|
||||
],
|
||||
"tagTranslation":{
|
||||
"風景":{
|
||||
"zh":"风景"
|
||||
},
|
||||
"背景":{
|
||||
"zh":"background"
|
||||
},
|
||||
"風景画":{
|
||||
"zh":"landscape painting"
|
||||
},
|
||||
"空":{
|
||||
"zh":"sky"
|
||||
},
|
||||
"雲":{
|
||||
"zh":"云"
|
||||
},
|
||||
"創作":{
|
||||
"zh":"原创"
|
||||
},
|
||||
"ファンタジー":{
|
||||
"zh":"奇幻"
|
||||
},
|
||||
"夏":{
|
||||
"zh":"夏天"
|
||||
},
|
||||
"青":{
|
||||
"zh":"蓝"
|
||||
},
|
||||
"建物":{
|
||||
"zh":"building"
|
||||
},
|
||||
"青空":{
|
||||
"zh":"蓝天"
|
||||
},
|
||||
"少女":{
|
||||
"zh":"young girl"
|
||||
},
|
||||
"東京":{
|
||||
"zh":"tokyo"
|
||||
},
|
||||
"抽象画":{
|
||||
"zh":"abstract art"
|
||||
},
|
||||
"男の子":{
|
||||
"zh":"男孩子"
|
||||
},
|
||||
"透明水彩":{
|
||||
"zh":"transparent watercolor"
|
||||
}
|
||||
},
|
||||
"zoneConfig":{
|
||||
"header":{
|
||||
"url":"https:\/\/pixon.ads-pixiv.net\/show?zone_id=header&format=js&s=1&up=0&a=22&ng=g&l=zh&uri=%2Fajax%2Fsearch%2Fartworks%2F_PARAM_&is_spa=1&K=59bba275c645c&ab_test_digits_first=20&yuid=FwdzEnA&suid=Pgfip96ymw5tvu9l9&num=5edb6277927"
|
||||
},
|
||||
"footer":{
|
||||
"url":"https:\/\/pixon.ads-pixiv.net\/show?zone_id=footer&format=js&s=1&up=0&a=22&ng=g&l=zh&uri=%2Fajax%2Fsearch%2Fartworks%2F_PARAM_&is_spa=1&K=59bba275c645c&ab_test_digits_first=20&yuid=FwdzEnA&suid=Pgfip96yn1fgocj2&num=5edb6277775"
|
||||
},
|
||||
"infeed":{
|
||||
"url":"https:\/\/pixon.ads-pixiv.net\/show?zone_id=illust_search_grid&format=js&s=1&up=0&a=22&ng=g&l=zh&uri=%2Fajax%2Fsearch%2Fartworks%2F_PARAM_&is_spa=1&K=59bba275c645c&ab_test_digits_first=20&yuid=FwdzEnA&suid=Pgfip96yn4t7cho88&num=5edb6277137"
|
||||
}
|
||||
},
|
||||
"extraData":{
|
||||
"meta":{
|
||||
"title":"#风景のイラスト・マンガ作品(投稿超过10万件) - pixiv",
|
||||
"description":"pixiv",
|
||||
"canonical":"https:\/\/www.pixiv.net\/tags\/%E9%A3%8E%E6%99%AF",
|
||||
"alternateLanguages":{
|
||||
"ja":"https:\/\/www.pixiv.net\/tags\/%E9%A3%8E%E6%99%AF",
|
||||
"en":"https:\/\/www.pixiv.net\/en\/tags\/%E9%A3%8E%E6%99%AF"
|
||||
},
|
||||
"descriptionHeader":"pixiv"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
#### 字段说明 ####
|
||||
- `novel`: (`Object`) 小说搜索结果
|
||||
- `data`: (`Object`) 搜索结果(仅当前页数)
|
||||
- (待补充)
|
||||
- `total`: (`number`) 搜索结果总量
|
||||
- `popular`: (`Object`) 受欢迎的搜索结果
|
||||
- `relatedTags`: (`string[]`) 与搜索结果相关的标签
|
||||
- `tagTranslation`: (`Object`) 相关标签的翻译信息
|
||||
- `{Attr: 标签名}`: 标签名为属性名
|
||||
- `语言名(例如 中文是 zh)`: (`string`) 标签翻译名
|
||||
- `zoneConfig`: (`Object`) 猜测是广告相关信息?
|
||||
- `extraData`: (`Object`) 扩展信息
|
||||
- `meta`: (`Object`) 网页元数据
|
||||
- `title`: (`string`) 网页标题
|
||||
- `description`: 搜索结果说明内容
|
||||
- `descriptionHeader`: (`string`) 说明内容的Html代码
|
||||
- `alternateLanguages`: (`Object`) 不明链接?
|
||||
- `{语言名}`: 对应语言的链接
|
||||
- `illustManga`: (`Object`) 漫画和插画的搜索结果
|
||||
- `total`: (`number`) 搜索结果总量
|
||||
- `data`: (`Object[]`) 搜索结果(仅当前页数)
|
||||
- `illustId`: (`string` -> `number`) 作品Id
|
||||
- `illustTitle`: (`string`) 作品标题
|
||||
- `id`: (`string` -> `number`) 与`illustId`一致, 猜测是以兼容旧版本为目录而存在
|
||||
- `title`: (`string`) 与`illustTitle`一致, 猜测是以兼容旧版本为目录而存在
|
||||
- `illustType`: (`number`) 作品类型
|
||||
- `0`: 插画作品
|
||||
- `1`: 漫画作品
|
||||
- `2`: 动图作品
|
||||
- `xRestrict`: (`number`) 作品是否为限制级, 基本准确, 少部分不一定(看Pixiv审核怎么理解了)
|
||||
- `0`: 非限制级内容(即非R18作品)
|
||||
- `1`: 限制级内容(即R18作品)
|
||||
- `restrict`: (`number`) 作品限制级(意义不明, 可能是兼容性问题?)?
|
||||
- `sl`: (`number`) 不明?
|
||||
- `url`: (`string`) 作品预览图链接, 需要`Referer`请求头
|
||||
- `description`: (`string`) 作品说明
|
||||
- `tags`: (`string[]`) 作品标签数组
|
||||
- `userId`: (`string` -> `number`) 作者用户Id
|
||||
- `userName`: (`string`) 作者用户名
|
||||
- `width`: (`number`) 作品长度
|
||||
- `height`: (`number`) 作品高度
|
||||
- `pageCount`: (`number`) 作品页数
|
||||
- `isBookmarkable`: (`boolean`) 不明?
|
||||
- `bookmarkData`: (`Unknown`) 不明?
|
||||
- `alt`: (`string`) 简略介绍信息(在图片加载失败时可提供给`img`标签使用)
|
||||
- `isAdContainer`: (`boolean`) 不明?
|
||||
- `titleCaptionTranslation`: (`Object`) 不明?
|
||||
- `workTitle`: (`Unknown`) 不明?
|
||||
- `workCaption`: (`Unknown`) 不明?
|
||||
- `createDate`: (`string`) 作品创建时间(或者是完成时间?)
|
||||
- `updateDate`: (`string`) 作品上传时间
|
||||
- `profileImageUrl`: (`string`) 作者用户头像图片链接
|
||||
|
@ -21,21 +21,21 @@ GET https://www.pixiv.net/ajax/top/{type}?mode={mode}&lang={lang}
|
||||
- `lang`: 语言(只写几个)
|
||||
- `zh`: 中文
|
||||
|
||||
> 是否需要登录: `是`
|
||||
> 是否为Pixiv接口标准返回格式: `是`
|
||||
> 是否需要Referer请求头: `未知`
|
||||
- 是否需要登录: `是`
|
||||
- 是否为Pixiv接口标准返回格式: `是`
|
||||
- 是否需要Referer请求头: `是`
|
||||
|
||||
请求Url示例:
|
||||
### 请求示例 ###
|
||||
```
|
||||
GET https://www.pixiv.net/ajax/top/illust?mode=all&lang=zh
|
||||
```
|
||||
|
||||
响应示例:
|
||||
### 返回数据 ###
|
||||
#### 数据示例 ####
|
||||
```
|
||||
(内容过长, 略)
|
||||
```
|
||||
|
||||
返回内容(Json):
|
||||
#### 字段说明 ####
|
||||
- `page`: 网页相关内容
|
||||
- `tags`: (`Object[]`) 热门标签
|
||||
- `tag`: (`String`) 标签名
|
||||
|
2
pom.xml
2
pom.xml
@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>net.lamgc</groupId>
|
||||
<artifactId>ContentGrabbingJi</artifactId>
|
||||
<version>2.5.2-20200604.2-SNAPSHOT</version>
|
||||
<version>2.5.2-20200606.1-SNAPSHOT</version>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
|
@ -1,91 +1,42 @@
|
||||
package net.lamgc.cgj.bot;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.gson.*;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import net.lamgc.cgj.bot.boot.BotGlobal;
|
||||
import net.lamgc.cgj.bot.cache.*;
|
||||
import net.lamgc.cgj.bot.cache.CacheStore;
|
||||
import net.lamgc.cgj.bot.cache.CacheStoreCentral;
|
||||
import net.lamgc.cgj.bot.cache.JsonRedisCacheStore;
|
||||
import net.lamgc.cgj.bot.event.BufferMessageEvent;
|
||||
import net.lamgc.cgj.bot.sort.PreLoadDataComparator;
|
||||
import net.lamgc.cgj.pixiv.PixivDownload;
|
||||
import net.lamgc.cgj.pixiv.PixivDownload.PageQuality;
|
||||
import net.lamgc.cgj.pixiv.PixivSearchBuilder;
|
||||
import net.lamgc.cgj.pixiv.PixivURL;
|
||||
import net.lamgc.cgj.pixiv.PixivDownload.PageQuality;
|
||||
import net.lamgc.cgj.pixiv.PixivURL.RankingContentType;
|
||||
import net.lamgc.cgj.pixiv.PixivURL.RankingMode;
|
||||
import net.lamgc.cgj.util.URLs;
|
||||
import net.lamgc.utils.base.runner.Argument;
|
||||
import net.lamgc.utils.base.runner.Command;
|
||||
import net.lz1998.cq.utils.CQCode;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpHead;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "SameParameterValue"})
|
||||
@SuppressWarnings({"SameParameterValue"})
|
||||
public class BotCommandProcess {
|
||||
|
||||
private final static PixivDownload pixivDownload =
|
||||
new PixivDownload(BotGlobal.getGlobal().getCookieStore(), BotGlobal.getGlobal().getProxy());
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(BotCommandProcess.class);
|
||||
|
||||
private final static File imageStoreDir = new File(BotGlobal.getGlobal().getDataStoreDir(), "data/image/cgj/");
|
||||
private final static Gson gson = new GsonBuilder()
|
||||
.serializeNulls()
|
||||
.create();
|
||||
|
||||
/* -------------------- 缓存 -------------------- */
|
||||
|
||||
private final static Hashtable<String, File> imageCache = new Hashtable<>();
|
||||
|
||||
/**
|
||||
* 作品信息缓存 - 不过期
|
||||
*/
|
||||
private final static CacheStore<JsonElement> illustInfoCache =
|
||||
new JsonRedisCacheStore(BotGlobal.getGlobal().getRedisServer(), "illustInfo", gson);
|
||||
|
||||
/**
|
||||
* 作品信息预加载数据 - 有效期 2 小时, 本地缓存有效期1 ± 0.25
|
||||
*/
|
||||
private final static CacheStore<JsonElement> illustPreLoadDataCache =
|
||||
CacheStoreUtils.hashLocalHotDataStore(
|
||||
new JsonRedisCacheStore(BotGlobal.getGlobal().getRedisServer(), "illustPreLoadData", gson),
|
||||
3600000, 900000);
|
||||
/**
|
||||
* 搜索内容缓存, 有效期 2 小时
|
||||
*/
|
||||
private final static CacheStore<JsonElement> searchBodyCache =
|
||||
new JsonRedisCacheStore(BotGlobal.getGlobal().getRedisServer(), "searchBody", gson);
|
||||
|
||||
/**
|
||||
* 排行榜缓存, 不过期
|
||||
*/
|
||||
private final static CacheStore<List<JsonObject>> rankingCache =
|
||||
new JsonObjectRedisListCacheStore(BotGlobal.getGlobal().getRedisServer(), "ranking", gson);
|
||||
|
||||
/**
|
||||
* 作品页面下载链接缓存 - 不过期
|
||||
*/
|
||||
private final static CacheStore<List<String>> pagesCache =
|
||||
new StringListRedisCacheStore(BotGlobal.getGlobal().getRedisServer(), "imagePages");
|
||||
|
||||
/**
|
||||
* 作品报告存储 - 不过期
|
||||
*/
|
||||
public final static CacheStore<JsonElement> reportStore =
|
||||
new JsonRedisCacheStore(BotGlobal.getGlobal().getRedisServer(), "report", gson);
|
||||
new JsonRedisCacheStore(BotGlobal.getGlobal().getRedisServer(),
|
||||
"report", BotGlobal.getGlobal().getGson());
|
||||
|
||||
private final static RankingUpdateTimer updateTimer = new RankingUpdateTimer();
|
||||
|
||||
@ -140,7 +91,8 @@ public class BotCommandProcess {
|
||||
* @return 返回作品信息
|
||||
*/
|
||||
@Command(commandName = "info")
|
||||
public static String artworkInfo(@Argument(name = "$fromGroup") long fromGroup, @Argument(name = "id") int illustId) {
|
||||
public static String artworkInfo(@Argument(name = "$fromGroup") long fromGroup,
|
||||
@Argument(name = "id") int illustId) {
|
||||
if(illustId <= 0) {
|
||||
return "这个作品Id是错误的!";
|
||||
}
|
||||
@ -150,7 +102,7 @@ public class BotCommandProcess {
|
||||
return "阅览禁止:该作品已被封印!!";
|
||||
}
|
||||
|
||||
JsonObject illustPreLoadData = getIllustPreLoadData(illustId, false);
|
||||
JsonObject illustPreLoadData = CacheStoreCentral.getIllustPreLoadData(illustId, false);
|
||||
// 在 Java 6 开始, 编译器会将用'+'进行的字符串拼接将自动转换成StringBuilder拼接
|
||||
return "色图姬帮你了解了这个作品的信息!\n" + "---------------- 作品信息 ----------------" +
|
||||
"\n作品Id: " + illustId +
|
||||
@ -164,7 +116,7 @@ public class BotCommandProcess {
|
||||
"\n页数:" + illustPreLoadData.get(PreLoadDataComparator.Attribute.PAGE.attrName).getAsInt() + "页" +
|
||||
"\n作品链接:" + artworksLink(fromGroup, illustId) + "\n" +
|
||||
"---------------- 作品图片 ----------------\n" +
|
||||
getImageById(fromGroup, illustId, PageQuality.REGULAR, 1) + "\n" +
|
||||
CacheStoreCentral.getImageById(fromGroup, illustId, PageQuality.REGULAR, 1) + "\n" +
|
||||
"使用 \".cgj image -id " +
|
||||
illustId +
|
||||
"\" 获取原图。\n如有不当作品,可使用\".cgj report -id " +
|
||||
@ -214,7 +166,8 @@ public class BotCommandProcess {
|
||||
PixivURL.RankingMode mode;
|
||||
try {
|
||||
String rankingModeValue = contentMode.toUpperCase();
|
||||
mode = PixivURL.RankingMode.valueOf(rankingModeValue.startsWith("MODE_") ? rankingModeValue : "MODE_" + rankingModeValue);
|
||||
mode = PixivURL.RankingMode.valueOf(rankingModeValue.startsWith("MODE_") ?
|
||||
rankingModeValue : "MODE_" + rankingModeValue);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("无效的RankingMode值: {}", contentMode);
|
||||
return "参数无效, 请查看帮助信息";
|
||||
@ -223,7 +176,8 @@ public class BotCommandProcess {
|
||||
PixivURL.RankingContentType type;
|
||||
try {
|
||||
String contentTypeValue = contentType.toUpperCase();
|
||||
type = PixivURL.RankingContentType.valueOf(contentTypeValue.startsWith("TYPE_") ? contentTypeValue : "TYPE_" + contentTypeValue);
|
||||
type = PixivURL.RankingContentType.valueOf(
|
||||
contentTypeValue.startsWith("TYPE_") ? contentTypeValue : "TYPE_" + contentTypeValue);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("无效的RankingContentType值: {}", contentType);
|
||||
return "参数无效, 请查看帮助信息";
|
||||
@ -235,7 +189,8 @@ public class BotCommandProcess {
|
||||
return "不支持的内容类型或模式!";
|
||||
}
|
||||
|
||||
StringBuilder resultBuilder = new StringBuilder(mode.name() + " - 以下是 ").append(new SimpleDateFormat("yyyy-MM-dd").format(queryDate)).append(" 的Pixiv插画排名榜前十名:\n");
|
||||
StringBuilder resultBuilder = new StringBuilder(mode.name() + " - 以下是 ")
|
||||
.append(new SimpleDateFormat("yyyy-MM-dd").format(queryDate)).append(" 的Pixiv插画排名榜前十名:\n");
|
||||
try {
|
||||
int index = 0;
|
||||
int itemLimit = 10;
|
||||
@ -256,7 +211,8 @@ public class BotCommandProcess {
|
||||
log.warn("配置项 {} 的参数值格式有误!", imageLimitPropertyKey);
|
||||
}
|
||||
|
||||
List<JsonObject> rankingInfoList = getRankingInfoByCache(type, mode, queryDate, 1, Math.max(0, itemLimit), false);
|
||||
List<JsonObject> rankingInfoList = CacheStoreCentral
|
||||
.getRankingInfoByCache(type, mode, queryDate, 1, Math.max(0, itemLimit), false);
|
||||
if(rankingInfoList.isEmpty()) {
|
||||
return "无法查询排行榜,可能排行榜尚未更新。";
|
||||
}
|
||||
@ -270,16 +226,21 @@ public class BotCommandProcess {
|
||||
String authorName = rankInfo.get("user_name").getAsString();
|
||||
String title = rankInfo.get("title").getAsString();
|
||||
resultBuilder.append(rank).append(". (id: ").append(illustId).append(") ").append(title)
|
||||
.append("(Author: ").append(authorName).append(",").append(authorId).append(") ").append(pagesCount).append("p.\n");
|
||||
.append("(Author: ").append(authorName).append(",").append(authorId).append(") ")
|
||||
.append(pagesCount).append("p.\n");
|
||||
if (index <= imageLimit) {
|
||||
resultBuilder.append(getImageById(fromGroup, illustId, PixivDownload.PageQuality.REGULAR, 1)).append("\n");
|
||||
resultBuilder
|
||||
.append(CacheStoreCentral
|
||||
.getImageById(fromGroup, illustId, PixivDownload.PageQuality.REGULAR, 1))
|
||||
.append("\n");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("消息处理异常", e);
|
||||
return "排名榜获取失败!详情请查看机器人控制台。";
|
||||
}
|
||||
return resultBuilder.append("如查询当前时间获取到昨天时间,则今日排名榜未更新。\n如有不当作品,可使用\".cgj report -id 作品id\"向色图姬反馈。").toString();
|
||||
return resultBuilder.append("如查询当前时间获取到昨天时间,则今日排名榜未更新。\n" +
|
||||
"如有不当作品,可使用\".cgj report -id 作品id\"向色图姬反馈。").toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -332,88 +293,8 @@ public class BotCommandProcess {
|
||||
@Argument(name = "page", force = false, defaultValue = "1") int pagesIndex
|
||||
) throws IOException {
|
||||
log.info("正在执行搜索...");
|
||||
PixivSearchBuilder searchBuilder = new PixivSearchBuilder(Strings.isNullOrEmpty(content) ? "" : content);
|
||||
if (type != null) {
|
||||
try {
|
||||
searchBuilder.setSearchType(PixivSearchBuilder.SearchType.valueOf(type.toUpperCase()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("不支持的SearchType: {}", type);
|
||||
}
|
||||
}
|
||||
if (area != null) {
|
||||
try {
|
||||
searchBuilder.setSearchArea(PixivSearchBuilder.SearchArea.valueOf(area));
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("不支持的SearchArea: {}", area);
|
||||
}
|
||||
}
|
||||
if (contentOption != null) {
|
||||
try {
|
||||
searchBuilder.setSearchContentOption(PixivSearchBuilder.SearchContentOption.valueOf(contentOption));
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("不支持的SearchContentOption: {}", contentOption);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Strings.isNullOrEmpty(includeKeywords)) {
|
||||
for (String keyword : includeKeywords.split(";")) {
|
||||
searchBuilder.removeExcludeKeyword(keyword.trim());
|
||||
searchBuilder.addIncludeKeyword(keyword.trim());
|
||||
log.debug("已添加关键字: {}", keyword);
|
||||
}
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(excludeKeywords)) {
|
||||
for (String keyword : excludeKeywords.split(";")) {
|
||||
searchBuilder.removeIncludeKeyword(keyword.trim());
|
||||
searchBuilder.addExcludeKeyword(keyword.trim());
|
||||
log.debug("已添加排除关键字: {}", keyword);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("正在搜索作品, 条件: {}", searchBuilder.getSearchCondition());
|
||||
|
||||
String requestUrl = searchBuilder.buildURL().intern();
|
||||
log.debug("RequestUrl: {}", requestUrl);
|
||||
JsonObject resultBody = null;
|
||||
if(!searchBodyCache.exists(requestUrl)) {
|
||||
synchronized (requestUrl) {
|
||||
if (!searchBodyCache.exists(requestUrl)) {
|
||||
log.debug("searchBody缓存失效, 正在更新...");
|
||||
JsonObject jsonObject;
|
||||
HttpGet httpGetRequest = pixivDownload.createHttpGetRequest(requestUrl);
|
||||
HttpResponse response = pixivDownload.getHttpClient().execute(httpGetRequest);
|
||||
|
||||
String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
log.debug("ResponseBody: {}", responseBody);
|
||||
jsonObject = gson.fromJson(responseBody, JsonObject.class);
|
||||
|
||||
if (jsonObject.get("error").getAsBoolean()) {
|
||||
log.error("接口请求错误, 错误信息: {}", jsonObject.get("message").getAsString());
|
||||
return "处理命令时发生错误!";
|
||||
}
|
||||
|
||||
long expire = 7200 * 1000;
|
||||
String propValue = SettingProperties
|
||||
.getProperty(SettingProperties.GLOBAL, "cache.searchBody.expire", "7200000");
|
||||
try {
|
||||
expire = Long.parseLong(propValue);
|
||||
} catch (Exception e) {
|
||||
log.warn("全局配置项 \"{}\" 值非法, 已使用默认值: {}", propValue, expire);
|
||||
}
|
||||
resultBody = jsonObject.getAsJsonObject().getAsJsonObject("body");
|
||||
searchBodyCache.update(requestUrl, jsonObject, expire);
|
||||
log.debug("searchBody缓存已更新(有效时间: {})", expire);
|
||||
} else {
|
||||
log.debug("搜索缓存命中.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.debug("搜索缓存命中.");
|
||||
}
|
||||
|
||||
if(Objects.isNull(resultBody)) {
|
||||
resultBody = searchBodyCache.getCache(requestUrl).getAsJsonObject().getAsJsonObject("body");
|
||||
}
|
||||
JsonObject resultBody = CacheStoreCentral
|
||||
.getSearchBody(content, type, area, includeKeywords, excludeKeywords, contentOption);
|
||||
|
||||
StringBuilder result = new StringBuilder("内容 " + content + " 的搜索结果:\n");
|
||||
log.debug("正在处理信息...");
|
||||
@ -424,8 +305,10 @@ public class BotCommandProcess {
|
||||
} catch (Exception e) {
|
||||
log.warn("参数转换异常!将使用默认值(" + limit + ")", e);
|
||||
}
|
||||
int totalCount = 0;
|
||||
for (PixivSearchBuilder.SearchArea searchArea : PixivSearchBuilder.SearchArea.values()) {
|
||||
if (!resultBody.has(searchArea.jsonKey) || resultBody.getAsJsonObject(searchArea.jsonKey).getAsJsonArray("data").size() == 0) {
|
||||
if (!resultBody.has(searchArea.jsonKey) ||
|
||||
resultBody.getAsJsonObject(searchArea.jsonKey).getAsJsonArray("data").size() == 0) {
|
||||
log.debug("返回数据不包含 {}", searchArea.jsonKey);
|
||||
continue;
|
||||
}
|
||||
@ -449,7 +332,8 @@ public class BotCommandProcess {
|
||||
StringBuilder builder = new StringBuilder("[");
|
||||
illustObj.get("tags").getAsJsonArray().forEach(el -> builder.append(el.getAsString()).append(", "));
|
||||
builder.replace(builder.length() - 2, builder.length(), "]");
|
||||
log.debug("{} ({} / {})\n\t作品id: {}, \n\t作者名(作者id): {} ({}), \n\t作品标题: {}, \n\t作品Tags: {}, \n\t页数: {}页, \n\t作品链接: {}",
|
||||
log.debug("{} ({} / {})\n\t作品id: {}, \n\t作者名(作者id): {} ({}), \n\t" +
|
||||
"作品标题: {}, \n\t作品Tags: {}, \n\t页数: {}页, \n\t作品链接: {}",
|
||||
searchArea.name(),
|
||||
count,
|
||||
illustsList.size(),
|
||||
@ -462,8 +346,9 @@ public class BotCommandProcess {
|
||||
PixivURL.getPixivRefererLink(illustId)
|
||||
);
|
||||
|
||||
String imageMsg = getImageById(fromGroup, illustId, PixivDownload.PageQuality.REGULAR, 1);
|
||||
if (isNoSafe(illustId, SettingProperties.getProperties(fromGroup), true)) {
|
||||
String imageMsg =
|
||||
CacheStoreCentral.getImageById(fromGroup, illustId, PixivDownload.PageQuality.REGULAR, 1);
|
||||
if (isNoSafe(illustId, SettingProperties.getProperties(fromGroup), false)) {
|
||||
log.warn("作品Id {} 为R-18作品, 跳过.", illustId);
|
||||
continue;
|
||||
} else if(isReported(illustId)) {
|
||||
@ -471,7 +356,7 @@ public class BotCommandProcess {
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonObject illustPreLoadData = getIllustPreLoadData(illustId, false);
|
||||
JsonObject illustPreLoadData = CacheStoreCentral.getIllustPreLoadData(illustId, false);
|
||||
result.append(searchArea.name()).append(" (").append(count).append(" / ")
|
||||
.append(limit).append(")\n\t作品id: ").append(illustId)
|
||||
.append(", \n\t作者名: ").append(illustObj.get("userName").getAsString())
|
||||
@ -487,12 +372,16 @@ public class BotCommandProcess {
|
||||
.append(illustPreLoadData.get(PreLoadDataComparator.Attribute.COMMENT.attrName).getAsInt())
|
||||
.append("\n").append(imageMsg).append("\n");
|
||||
count++;
|
||||
totalCount++;
|
||||
}
|
||||
if (count > limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Strings.nullToEmpty(result.toString()) + "预览图片并非原图,使用“.cgj image -id 作品id”获取原图\n如有不当作品,可使用\".cgj report -id 作品id\"向色图姬反馈。";
|
||||
return totalCount <= 0 ?
|
||||
"搜索完成,未找到相关作品。" :
|
||||
Strings.nullToEmpty(result.toString()) + "预览图片并非原图,使用“.cgj image -id 作品id”获取原图\n" +
|
||||
"如有不当作品,可使用\".cgj report -id 作品id\"向色图姬反馈。";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -511,8 +400,13 @@ public class BotCommandProcess {
|
||||
log.warn("来源群 {} 查询的作品Id {} 为R18作品, 根据配置设定, 屏蔽该作品.", fromGroup, illustId);
|
||||
return "该作品已被封印!";
|
||||
}
|
||||
List<String> pagesList = PixivDownload.getIllustAllPageDownload(pixivDownload.getHttpClient(), pixivDownload.getCookieStore(), illustId, quality);
|
||||
StringBuilder builder = new StringBuilder("作品ID ").append(illustId).append(" 共有").append(pagesList.size()).append("页:").append("\n");
|
||||
List<String> pagesList =
|
||||
PixivDownload.getIllustAllPageDownload(
|
||||
BotGlobal.getGlobal().getPixivDownload().getHttpClient(),
|
||||
BotGlobal.getGlobal().getPixivDownload().getCookieStore(),
|
||||
illustId, quality);
|
||||
StringBuilder builder = new StringBuilder("作品ID ").append(illustId)
|
||||
.append(" 共有").append(pagesList.size()).append("页:").append("\n");
|
||||
int index = 0;
|
||||
for (String link : pagesList) {
|
||||
builder.append("Page ").append(++index).append(": ").append(link).append("\n");
|
||||
@ -531,7 +425,8 @@ public class BotCommandProcess {
|
||||
* @return 返回作品在Pixiv的链接
|
||||
*/
|
||||
@Command(commandName = "link")
|
||||
public static String artworksLink(@Argument(name = "$fromGroup") long fromGroup, @Argument(name = "id") int illustId) {
|
||||
public static String artworksLink(@Argument(name = "$fromGroup") long fromGroup,
|
||||
@Argument(name = "id") int illustId) {
|
||||
try {
|
||||
if (isNoSafe(illustId, SettingProperties.getProperties(fromGroup), false)) {
|
||||
log.warn("作品Id {} 已被屏蔽.", illustId);
|
||||
@ -547,120 +442,9 @@ public class BotCommandProcess {
|
||||
return PixivURL.getPixivRefererLink(illustId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过illustId获取作品图片
|
||||
* @param fromGroup 来源群(系统提供)
|
||||
* @param illustId 作品Id
|
||||
* @param quality 图片质量
|
||||
* @param pageIndex 指定页面索引, 从1开始
|
||||
* @return 如果成功, 返回BotCode, 否则返回错误信息.
|
||||
*/
|
||||
@Command(commandName = "image")
|
||||
public static String getImageById(
|
||||
@Argument(name = "$fromGroup") long fromGroup,
|
||||
@Argument(name = "id") int illustId,
|
||||
@Argument(name = "quality", force = false) PixivDownload.PageQuality quality,
|
||||
@Argument(name = "page", force = false, defaultValue = "1") int pageIndex) {
|
||||
log.debug("IllustId: {}, Quality: {}, PageIndex: {}", illustId, quality.name(), pageIndex);
|
||||
|
||||
try {
|
||||
if (isNoSafe(illustId, SettingProperties.getProperties(fromGroup), false)) {
|
||||
log.warn("作品 {} 存在R-18内容且设置\"image.allowR18\"为false,将屏蔽该作品不发送.", illustId);
|
||||
return "(根据设置,该作品已被屏蔽!)";
|
||||
} else if(isReported(illustId)) {
|
||||
log.warn("作品Id {} 被报告, 正在等待审核, 跳过该作品.", illustId);
|
||||
return "(该作品已被封印)";
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("作品信息无法获取!", e);
|
||||
return "(发生网络异常,无法获取图片!)";
|
||||
}
|
||||
|
||||
List<String> pagesList;
|
||||
try {
|
||||
pagesList = getIllustPages(illustId, quality, false);
|
||||
} catch (IOException e) {
|
||||
log.error("获取下载链接列表时发生异常", e);
|
||||
return "发生网络异常,无法获取图片!";
|
||||
}
|
||||
|
||||
if(log.isDebugEnabled()) {
|
||||
StringBuilder logBuilder = new StringBuilder("作品Id " + illustId + " 所有页面下载链接: \n");
|
||||
AtomicInteger index = new AtomicInteger();
|
||||
pagesList.forEach(item -> logBuilder.append(index.incrementAndGet()).append(". ").append(item).append("\n"));
|
||||
log.debug(logBuilder.toString());
|
||||
}
|
||||
|
||||
if (pagesList.size() < pageIndex || pageIndex <= 0) {
|
||||
log.warn("指定的页数超出了总页数({} / {})", pageIndex, pagesList.size());
|
||||
return "指定的页数超出了范围(总共 " + pagesList.size() + " 页)";
|
||||
}
|
||||
|
||||
String downloadLink = pagesList.get(pageIndex - 1);
|
||||
String fileName = URLs.getResourceName(Strings.nullToEmpty(downloadLink));
|
||||
File imageFile = new File(getImageStoreDir(), downloadLink.substring(downloadLink.lastIndexOf("/") + 1));
|
||||
log.debug("FileName: {}, DownloadLink: {}", fileName, downloadLink);
|
||||
if(!imageCache.containsKey(fileName)) {
|
||||
if(imageFile.exists()) {
|
||||
HttpHead headRequest = new HttpHead(downloadLink);
|
||||
headRequest.addHeader("Referer", PixivURL.getPixivRefererLink(illustId));
|
||||
HttpResponse headResponse;
|
||||
try {
|
||||
headResponse = pixivDownload.getHttpClient().execute(headRequest);
|
||||
} catch (IOException e) {
|
||||
log.error("获取图片大小失败!", e);
|
||||
return "图片获取失败!";
|
||||
}
|
||||
String contentLengthStr = headResponse.getFirstHeader(HttpHeaderNames.CONTENT_LENGTH.toString()).getValue();
|
||||
log.debug("图片大小: {}B", contentLengthStr);
|
||||
if (imageFile.length() == Long.parseLong(contentLengthStr)) {
|
||||
imageCache.put(URLs.getResourceName(downloadLink), imageFile);
|
||||
log.debug("作品Id {} 第 {} 页缓存已补充.", illustId, pageIndex);
|
||||
return getImageToBotCode(imageFile, false).toString();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Throwable throwable = ImageCacheStore.executeCacheRequest(new ImageCacheObject(imageCache, illustId, downloadLink, imageFile));
|
||||
if(throwable != null) {
|
||||
throw throwable;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("图片缓存被中断", e);
|
||||
return "(错误:图片获取超时)";
|
||||
} catch (Throwable e) {
|
||||
log.error("图片 {} 获取失败:\n{}", illustId + "p" + pageIndex, Throwables.getStackTraceAsString(e));
|
||||
return "(错误: 图片获取出错)";
|
||||
}
|
||||
} else {
|
||||
log.debug("图片 {} 缓存命中.", fileName);
|
||||
}
|
||||
|
||||
return getImageToBotCode(imageCache.get(fileName), false).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过文件获取图片的BotCode代码
|
||||
* @param targetFile 图片文件
|
||||
* @param updateCache 是否刷新缓存(只是让机器人重新上传, 如果上传接口有重复检测的话是无法处理的)
|
||||
* @return 返回设定好参数的BotCode
|
||||
*/
|
||||
private static BotCode getImageToBotCode(File targetFile, boolean updateCache) {
|
||||
String fileName = Objects.requireNonNull(targetFile, "targetFile is null").getName();
|
||||
BotCode code = BotCode.parse(CQCode.image(getImageStoreDir().getName() + "/" + fileName));
|
||||
code.addParameter("absolutePath", targetFile.getAbsolutePath());
|
||||
code.addParameter("imageName", fileName.substring(0, fileName.lastIndexOf(".")));
|
||||
code.addParameter("updateCache", updateCache ? "true" : "false");
|
||||
return code;
|
||||
}
|
||||
|
||||
static void clearCache() {
|
||||
log.warn("正在清除所有缓存...");
|
||||
imageCache.clear();
|
||||
illustInfoCache.clear();
|
||||
illustPreLoadDataCache.clear();
|
||||
pagesCache.clear();
|
||||
searchBodyCache.clear();
|
||||
CacheStoreCentral.clearCache();
|
||||
File imageStoreDir = new File(BotGlobal.getGlobal().getDataStoreDir(), "data/image/cgj/");
|
||||
File[] listFiles = imageStoreDir.listFiles();
|
||||
if (listFiles == null) {
|
||||
@ -717,176 +501,23 @@ public class BotCommandProcess {
|
||||
* @throws IOException 获取数据时发生异常时抛出
|
||||
* @throws NoSuchElementException 当作品不存在时抛出
|
||||
*/
|
||||
public static boolean isNoSafe(int illustId, Properties settingProp, boolean returnRaw) throws IOException, NoSuchElementException {
|
||||
boolean rawValue = getIllustInfo(illustId, false).getAsJsonArray("tags").contains(new JsonPrimitive("R-18"));
|
||||
return returnRaw || settingProp == null ? rawValue : rawValue && !settingProp.getProperty("image.allowR18", "false").equalsIgnoreCase("true");
|
||||
public static boolean isNoSafe(int illustId, Properties settingProp, boolean returnRaw)
|
||||
throws IOException, NoSuchElementException {
|
||||
JsonObject illustInfo = CacheStoreCentral.getIllustInfo(illustId, false);
|
||||
JsonArray tags = illustInfo.getAsJsonArray("tags");
|
||||
boolean rawValue = illustInfo.get("xRestrict").getAsInt() != 0;
|
||||
if(!rawValue) {
|
||||
for(JsonElement tag : tags) {
|
||||
boolean current = tag.getAsString().matches("R-*18") || tag.getAsString().contains("R18");
|
||||
if (current) {
|
||||
rawValue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return returnRaw || settingProp == null ? rawValue :
|
||||
rawValue && !settingProp.getProperty("image.allowR18", "false")
|
||||
.equalsIgnoreCase("true");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取作品信息
|
||||
* @param illustId 作品Id
|
||||
* @param flushCache 强制刷新缓存
|
||||
* @return 返回作品信息
|
||||
* @throws IOException 当Http请求发生异常时抛出
|
||||
* @throws NoSuchElementException 当作品未找到时抛出
|
||||
*/
|
||||
private static JsonObject getIllustInfo(int illustId, boolean flushCache) throws IOException, NoSuchElementException {
|
||||
String illustIdStr = buildSyncKey(Integer.toString(illustId));
|
||||
JsonObject illustInfoObj = null;
|
||||
if (!illustInfoCache.exists(illustIdStr) || flushCache) {
|
||||
synchronized (illustIdStr) {
|
||||
if (!illustInfoCache.exists(illustIdStr) || flushCache) {
|
||||
illustInfoObj = pixivDownload.getIllustInfoByIllustId(illustId);
|
||||
illustInfoCache.update(illustIdStr, illustInfoObj, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(Objects.isNull(illustInfoObj)) {
|
||||
illustInfoObj = illustInfoCache.getCache(illustIdStr).getAsJsonObject();
|
||||
log.debug("作品Id {} IllustInfo缓存命中.", illustId);
|
||||
}
|
||||
return illustInfoObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取作品预加载数据.
|
||||
* 可以获取作品的一些与用户相关的信息
|
||||
* @param illustId 作品Id
|
||||
* @param flushCache 是否刷新缓存
|
||||
* @return 成功返回JsonObject对象
|
||||
* @throws IOException 当Http请求处理发生异常时抛出
|
||||
*/
|
||||
public static JsonObject getIllustPreLoadData(int illustId, boolean flushCache) throws IOException {
|
||||
String illustIdStr = buildSyncKey(Integer.toString(illustId));
|
||||
JsonObject result = null;
|
||||
if (!illustPreLoadDataCache.exists(illustIdStr) || flushCache) {
|
||||
synchronized (illustIdStr) {
|
||||
if (!illustPreLoadDataCache.exists(illustIdStr) || flushCache) {
|
||||
log.debug("IllustId {} 缓存失效, 正在更新...", illustId);
|
||||
JsonObject preLoadDataObj = pixivDownload.getIllustPreLoadDataById(illustId)
|
||||
.getAsJsonObject("illust")
|
||||
.getAsJsonObject(Integer.toString(illustId));
|
||||
|
||||
long expire = 7200 * 1000;
|
||||
String propValue = SettingProperties.
|
||||
getProperty(SettingProperties.GLOBAL, "cache.illustPreLoadData.expire", "7200000");
|
||||
log.debug("PreLoadData有效时间设定: {}", propValue);
|
||||
try {
|
||||
expire = Long.parseLong(propValue);
|
||||
} catch (Exception e) {
|
||||
log.warn("全局配置项 \"{}\" 值非法, 已使用默认值: {}", propValue, expire);
|
||||
}
|
||||
|
||||
result = preLoadDataObj;
|
||||
illustPreLoadDataCache.update(illustIdStr, preLoadDataObj, expire);
|
||||
log.debug("作品Id {} preLoadData缓存已更新(有效时间: {})", illustId, expire);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(Objects.isNull(result)) {
|
||||
result = illustPreLoadDataCache.getCache(illustIdStr).getAsJsonObject();
|
||||
log.debug("作品Id {} PreLoadData缓存命中.", illustId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<String> getIllustPages(int illustId, PixivDownload.PageQuality quality, boolean flushCache) throws IOException {
|
||||
String pagesSign = buildSyncKey(Integer.toString(illustId), ".", quality.name());
|
||||
List<String> result = null;
|
||||
if (!pagesCache.exists(pagesSign) || flushCache) {
|
||||
synchronized (pagesSign) {
|
||||
if (!pagesCache.exists(pagesSign) || flushCache) {
|
||||
List<String> linkList = PixivDownload.getIllustAllPageDownload(pixivDownload.getHttpClient(), pixivDownload.getCookieStore(), illustId, quality);
|
||||
result = linkList;
|
||||
pagesCache.update(pagesSign, linkList, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(Objects.isNull(result)) {
|
||||
result = pagesCache.getCache(pagesSign);
|
||||
log.debug("作品Id {} Pages缓存命中.", illustId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片存储目录.
|
||||
* <p>每次调用都会检查目录是否存在, 如不存在则会抛出异常</p>
|
||||
* @return 返回File对象
|
||||
* @throws RuntimeException 当目录创建失败时将包装{@link IOException}异常并抛出.
|
||||
*/
|
||||
private static File getImageStoreDir() {
|
||||
if(!imageStoreDir.exists() && !Files.isSymbolicLink(imageStoreDir.toPath())) {
|
||||
if(!imageStoreDir.mkdirs()) {
|
||||
log.warn("酷Q图片缓存目录失效!(Path: {} )", imageStoreDir.getAbsolutePath());
|
||||
throw new RuntimeException(new IOException("文件夹创建失败!"));
|
||||
}
|
||||
}
|
||||
return imageStoreDir;
|
||||
}
|
||||
|
||||
|
||||
private final static Random expireTimeFloatRandom = new Random();
|
||||
/**
|
||||
* 获取排行榜
|
||||
* @param contentType 排行榜类型
|
||||
* @param mode 排行榜模式
|
||||
* @param queryDate 查询时间
|
||||
* @param start 开始排名, 从1开始
|
||||
* @param range 取范围
|
||||
* @param flushCache 是否强制刷新缓存
|
||||
* @return 成功返回有值List, 失败且无异常返回空
|
||||
* @throws IOException 获取异常时抛出
|
||||
*/
|
||||
public static List<JsonObject> getRankingInfoByCache(PixivURL.RankingContentType contentType, PixivURL.RankingMode mode, Date queryDate, int start, int range, boolean flushCache) throws IOException {
|
||||
if(!contentType.isSupportedMode(mode)) {
|
||||
log.warn("试图获取不支持的排行榜类型已拒绝.(ContentType: {}, RankingMode: {})", contentType.name(), mode.name());
|
||||
if(log.isDebugEnabled()) {
|
||||
try {
|
||||
Thread.dumpStack();
|
||||
} catch(Exception e) {
|
||||
log.debug("本次非法请求的堆栈信息如下: \n{}", Throwables.getStackTraceAsString(e));
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
|
||||
String date = new SimpleDateFormat("yyyyMMdd").format(queryDate);
|
||||
String requestSign = buildSyncKey(contentType.name(), ".", mode.name(), ".", date);
|
||||
List<JsonObject> result = null;
|
||||
if(!rankingCache.exists(requestSign) || flushCache) {
|
||||
synchronized(requestSign) {
|
||||
if(!rankingCache.exists(requestSign) || flushCache) {
|
||||
log.debug("Ranking缓存失效, 正在更新...(RequestSign: {})", requestSign);
|
||||
List<JsonObject> rankingResult = pixivDownload.getRanking(contentType, mode, queryDate, 1, 500);
|
||||
long expireTime = 0;
|
||||
if(rankingResult.size() == 0) {
|
||||
expireTime = 5400000 + expireTimeFloatRandom.nextInt(1800000);
|
||||
log.warn("数据获取失败, 将设置浮动有效时间以准备下次更新. (ExpireTime: {}ms)", expireTime);
|
||||
}
|
||||
result = new ArrayList<>(rankingResult).subList(start - 1, start + range - 1);
|
||||
rankingCache.update(requestSign, rankingResult, expireTime);
|
||||
log.debug("Ranking缓存更新完成.(RequestSign: {})", requestSign);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Objects.isNull(result)) {
|
||||
result = rankingCache.getCache(requestSign, start - 1, range);
|
||||
log.debug("RequestSign [{}] 缓存命中.", requestSign);
|
||||
}
|
||||
log.debug("Result-Length: {}", result.size());
|
||||
return PixivDownload.getRanking(result, start - 1, range);
|
||||
}
|
||||
|
||||
private static String buildSyncKey(String... keys) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String string : keys) {
|
||||
sb.append(string);
|
||||
}
|
||||
return sb.toString().intern();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.lamgc.cgj.bot;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import net.lamgc.cgj.bot.cache.CacheStoreCentral;
|
||||
import net.lamgc.cgj.bot.message.MessageSender;
|
||||
import net.lamgc.cgj.pixiv.PixivDownload;
|
||||
import net.lamgc.cgj.pixiv.PixivURL;
|
||||
@ -61,7 +62,7 @@ public class RandomRankingArtworksSender extends AutoSender {
|
||||
|
||||
int selectRanking = rankingStart + new Random().nextInt(rankingStop - rankingStart + 1);
|
||||
try {
|
||||
List<JsonObject> rankingList = BotCommandProcess.getRankingInfoByCache(
|
||||
List<JsonObject> rankingList = CacheStoreCentral.getRankingInfoByCache(
|
||||
contentType,
|
||||
mode,
|
||||
queryDate,
|
||||
@ -76,7 +77,8 @@ public class RandomRankingArtworksSender extends AutoSender {
|
||||
|
||||
JsonObject rankingInfo = rankingList.get(0);
|
||||
int illustId = rankingInfo.get("illust_id").getAsInt();
|
||||
if(BotCommandProcess.isNoSafe(illustId, SettingProperties.getProperties(SettingProperties.GLOBAL), false)) {
|
||||
if(BotCommandProcess.isNoSafe(illustId,
|
||||
SettingProperties.getProperties(SettingProperties.GLOBAL), false)) {
|
||||
log.warn("作品为r18作品, 取消本次发送.");
|
||||
return;
|
||||
} else if(BotCommandProcess.isReported(illustId)) {
|
||||
@ -84,13 +86,12 @@ public class RandomRankingArtworksSender extends AutoSender {
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append("#美图推送 - 今日排行榜 第 ").append(rankingInfo.get("rank").getAsInt()).append(" 名\n");
|
||||
message.append("标题:").append(rankingInfo.get("title").getAsString()).append("(").append(illustId).append(")\n");
|
||||
message.append("作者:").append(rankingInfo.get("user_name").getAsString()).append("\n");
|
||||
message.append(BotCommandProcess.getImageById(0, illustId, quality, 1));
|
||||
message.append("\n如有不当作品,可使用\".cgj report -id ").append(illustId).append("\"向色图姬反馈。");
|
||||
getMessageSender().sendMessage(message.toString());
|
||||
String message = "#美图推送 - 今日排行榜 第 " + rankingInfo.get("rank").getAsInt() + " 名\n" +
|
||||
"标题:" + rankingInfo.get("title").getAsString() + "(" + illustId + ")\n" +
|
||||
"作者:" + rankingInfo.get("user_name").getAsString() + "\n" +
|
||||
CacheStoreCentral.getImageById(0, illustId, quality, 1) +
|
||||
"\n如有不当作品,可使用\".cgj report -id " + illustId + "\"向色图姬反馈。";
|
||||
getMessageSender().sendMessage(message);
|
||||
} catch (Exception e) {
|
||||
log.error("发送随机作品时发生异常", e);
|
||||
}
|
||||
|
@ -51,6 +51,10 @@ public final class SettingProperties {
|
||||
long groupId;
|
||||
try {
|
||||
groupId = Long.parseLong(name.substring(name.indexOf("group.") + 6, name.lastIndexOf(".properties")));
|
||||
if(groupId <= 0) {
|
||||
log.warn("无效的群配置文件: {}", groupId);
|
||||
continue;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("非法的配置文件名: {}", name);
|
||||
continue;
|
||||
@ -233,7 +237,7 @@ public final class SettingProperties {
|
||||
* @return 如果群组存在所属Properties, 则返回群组Properties, 否则返回GlobalProperties.
|
||||
*/
|
||||
public static Properties getProperties(long groupId) {
|
||||
if(groupPropMap.containsKey(groupId)) {
|
||||
if(groupId > 0 && groupPropMap.containsKey(groupId)) {
|
||||
return groupPropMap.get(groupId);
|
||||
}
|
||||
return getGlobalProperties();
|
||||
|
@ -1,6 +1,9 @@
|
||||
package net.lamgc.cgj.bot.boot;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import net.lamgc.cgj.pixiv.PixivDownload;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.client.CookieStore;
|
||||
import org.slf4j.Logger;
|
||||
@ -8,12 +11,16 @@ import org.slf4j.LoggerFactory;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
|
||||
public final class BotGlobal {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(BotGlobal.class);
|
||||
|
||||
private final static BotGlobal instance = new BotGlobal();
|
||||
|
||||
public static BotGlobal getGlobal() {
|
||||
@ -23,8 +30,6 @@ public final class BotGlobal {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(BotGlobal.class);
|
||||
|
||||
private final URI redisUri;
|
||||
|
||||
/**
|
||||
@ -38,6 +43,14 @@ public final class BotGlobal {
|
||||
|
||||
private CookieStore cookieStore;
|
||||
|
||||
private final Gson gson = new GsonBuilder()
|
||||
.serializeNulls()
|
||||
.create();
|
||||
|
||||
private PixivDownload pixivDownload;
|
||||
|
||||
private final File imageStoreDir;
|
||||
|
||||
private BotGlobal() {
|
||||
this.redisUri = URI.create("redis://" + System.getProperty("cgj.redisAddress"));
|
||||
this.redisServer = new JedisPool(
|
||||
@ -47,13 +60,15 @@ public final class BotGlobal {
|
||||
this.dataStoreDir = new File((!dataStoreDirPath.endsWith("/") || !dataStoreDirPath.endsWith("\\")) ?
|
||||
dataStoreDirPath + System.getProperty("file.separator") : dataStoreDirPath);
|
||||
|
||||
this.imageStoreDir = new File(getDataStoreDir(), "data/image/cgj/");
|
||||
|
||||
String proxyAddress = System.getProperty("cgj.proxy");
|
||||
HttpHost temp = null;
|
||||
if(!Strings.isNullOrEmpty(proxyAddress)) {
|
||||
try {
|
||||
URL proxyUrl = new URL(proxyAddress);
|
||||
temp = new HttpHost(proxyUrl.getHost(), proxyUrl.getPort());
|
||||
log.info("已启用Http协议代理:{}", temp.toHostString());
|
||||
log.info("已启用代理:{}", temp.toHostString());
|
||||
} catch (MalformedURLException e) {
|
||||
log.error("Proxy地址解析失败, 代理将不会启用.", e);
|
||||
}
|
||||
@ -80,12 +95,37 @@ public final class BotGlobal {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
|
||||
public CookieStore getCookieStore() {
|
||||
if(pixivDownload == null) {
|
||||
throw new IllegalStateException("CookieStore needs to be set before PixivDownload can be obtained");
|
||||
}
|
||||
return cookieStore;
|
||||
}
|
||||
|
||||
public void setCookieStore(CookieStore cookieStore) {
|
||||
if(this.cookieStore != null) {
|
||||
throw new IllegalStateException("CookieStore set");
|
||||
}
|
||||
this.cookieStore = cookieStore;
|
||||
this.pixivDownload =
|
||||
new PixivDownload(cookieStore, proxy);
|
||||
}
|
||||
|
||||
public Gson getGson() {
|
||||
return gson;
|
||||
}
|
||||
|
||||
public PixivDownload getPixivDownload() {
|
||||
return pixivDownload;
|
||||
}
|
||||
|
||||
public File getImageStoreDir() {
|
||||
if(!imageStoreDir.exists() && !Files.isSymbolicLink(imageStoreDir.toPath())) {
|
||||
if(!imageStoreDir.mkdirs()) {
|
||||
log.warn("酷Q图片缓存目录失效!(Path: {} )", imageStoreDir.getAbsolutePath());
|
||||
throw new RuntimeException(new IOException("文件夹创建失败!"));
|
||||
}
|
||||
}
|
||||
return imageStoreDir;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ public interface CacheStore<T> {
|
||||
* 更新或添加缓存项
|
||||
* @param key 缓存键名
|
||||
* @param value 缓存值
|
||||
* @param expire 有效期, 单位为ms(毫秒), 如不过期传入0或赋值
|
||||
* @param expire 有效期, 单位为ms(毫秒), 如不过期传入0或负数
|
||||
*/
|
||||
void update(String key, T value, long expire);
|
||||
|
||||
|
635
src/main/java/net/lamgc/cgj/bot/cache/CacheStoreCentral.java
vendored
Normal file
635
src/main/java/net/lamgc/cgj/bot/cache/CacheStoreCentral.java
vendored
Normal file
@ -0,0 +1,635 @@
|
||||
package net.lamgc.cgj.bot.cache;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import net.lamgc.cgj.bot.BotCode;
|
||||
import net.lamgc.cgj.bot.BotCommandProcess;
|
||||
import net.lamgc.cgj.bot.SettingProperties;
|
||||
import net.lamgc.cgj.bot.boot.BotGlobal;
|
||||
import net.lamgc.cgj.pixiv.PixivDownload;
|
||||
import net.lamgc.cgj.pixiv.PixivSearchBuilder;
|
||||
import net.lamgc.cgj.pixiv.PixivURL;
|
||||
import net.lamgc.cgj.util.URLs;
|
||||
import net.lamgc.utils.base.runner.Argument;
|
||||
import net.lamgc.utils.base.runner.Command;
|
||||
import net.lamgc.utils.encrypt.MessageDigestUtils;
|
||||
import net.lz1998.cq.utils.CQCode;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.apache.tomcat.util.http.fileupload.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
|
||||
public final class CacheStoreCentral {
|
||||
|
||||
private CacheStoreCentral() {}
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(CacheStoreCentral.class);
|
||||
|
||||
private final static Hashtable<String, File> imageCache = new Hashtable<>();
|
||||
|
||||
private final static JsonRedisCacheStore imageChecksumCache =
|
||||
new JsonRedisCacheStore(BotGlobal.getGlobal().getRedisServer(),
|
||||
"imageChecksum", BotGlobal.getGlobal().getGson());
|
||||
|
||||
/**
|
||||
* 作品信息缓存 - 不过期
|
||||
*/
|
||||
private final static CacheStore<JsonElement> illustInfoCache =
|
||||
new JsonRedisCacheStore(BotGlobal.getGlobal().getRedisServer(),
|
||||
"illustInfo", BotGlobal.getGlobal().getGson());
|
||||
|
||||
/**
|
||||
* 作品信息预加载数据 - 有效期 2 小时, 本地缓存有效期1 ± 0.25
|
||||
*/
|
||||
private final static CacheStore<JsonElement> illustPreLoadDataCache =
|
||||
CacheStoreUtils.hashLocalHotDataStore(
|
||||
new JsonRedisCacheStore(BotGlobal.getGlobal().getRedisServer(),
|
||||
"illustPreLoadData", BotGlobal.getGlobal().getGson()),
|
||||
3600000, 900000);
|
||||
/**
|
||||
* 搜索内容缓存, 有效期 2 小时
|
||||
*/
|
||||
private final static CacheStore<JsonElement> searchBodyCache =
|
||||
new JsonRedisCacheStore(BotGlobal.getGlobal().getRedisServer(),
|
||||
"searchBody", BotGlobal.getGlobal().getGson());
|
||||
|
||||
/**
|
||||
* 排行榜缓存, 不过期
|
||||
*/
|
||||
private final static CacheStore<List<JsonObject>> rankingCache =
|
||||
new JsonObjectRedisListCacheStore(BotGlobal.getGlobal().getRedisServer(),
|
||||
"ranking", BotGlobal.getGlobal().getGson());
|
||||
|
||||
/**
|
||||
* 作品页面下载链接缓存 - 不过期
|
||||
*/
|
||||
private final static CacheStore<List<String>> pagesCache =
|
||||
new StringListRedisCacheStore(BotGlobal.getGlobal().getRedisServer(), "imagePages");
|
||||
|
||||
/**
|
||||
* 清空所有缓存
|
||||
*/
|
||||
public static void clearCache() {
|
||||
imageCache.clear();
|
||||
illustInfoCache.clear();
|
||||
illustPreLoadDataCache.clear();
|
||||
searchBodyCache.clear();
|
||||
rankingCache.clear();
|
||||
pagesCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过illustId获取作品图片
|
||||
* @param fromGroup 来源群(系统提供)
|
||||
* @param illustId 作品Id
|
||||
* @param quality 图片质量
|
||||
* @param pageIndex 指定页面索引, 从1开始
|
||||
* @return 如果成功, 返回BotCode, 否则返回错误信息.
|
||||
*/
|
||||
@Command(commandName = "image")
|
||||
public static String getImageById(
|
||||
@Argument(name = "$fromGroup") long fromGroup,
|
||||
@Argument(name = "id") int illustId,
|
||||
@Argument(name = "quality", force = false) PixivDownload.PageQuality quality,
|
||||
@Argument(name = "page", force = false, defaultValue = "1") int pageIndex) {
|
||||
log.debug("IllustId: {}, Quality: {}, PageIndex: {}", illustId, quality.name(), pageIndex);
|
||||
|
||||
try {
|
||||
if (BotCommandProcess.isNoSafe(illustId, SettingProperties.getProperties(fromGroup), false)) {
|
||||
log.warn("作品 {} 存在R-18内容且设置\"image.allowR18\"为false,将屏蔽该作品不发送.", illustId);
|
||||
return "(根据设置,该作品已被屏蔽!)";
|
||||
} else if(BotCommandProcess.isReported(illustId)) {
|
||||
log.warn("作品Id {} 被报告, 正在等待审核, 跳过该作品.", illustId);
|
||||
return "(该作品已被封印)";
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("作品信息无法获取!", e);
|
||||
return "(发生网络异常,无法获取图片!)";
|
||||
}
|
||||
|
||||
List<String> pagesList;
|
||||
try {
|
||||
pagesList = CacheStoreCentral.getIllustPages(illustId, quality, false);
|
||||
} catch (IOException e) {
|
||||
log.error("获取下载链接列表时发生异常", e);
|
||||
return "发生网络异常,无法获取图片!";
|
||||
}
|
||||
|
||||
if(log.isDebugEnabled()) {
|
||||
StringBuilder logBuilder = new StringBuilder("作品Id " + illustId + " 所有页面下载链接: \n");
|
||||
AtomicInteger index = new AtomicInteger();
|
||||
pagesList.forEach(item ->
|
||||
logBuilder.append(index.incrementAndGet()).append(". ").append(item).append("\n"));
|
||||
log.debug(logBuilder.toString());
|
||||
}
|
||||
|
||||
if (pagesList.size() < pageIndex || pageIndex <= 0) {
|
||||
log.warn("指定的页数超出了总页数({} / {})", pageIndex, pagesList.size());
|
||||
return "指定的页数超出了范围(总共 " + pagesList.size() + " 页)";
|
||||
}
|
||||
|
||||
String downloadLink = pagesList.get(pageIndex - 1);
|
||||
String fileName = URLs.getResourceName(Strings.nullToEmpty(downloadLink));
|
||||
File imageFile = new File(BotGlobal.getGlobal().getImageStoreDir(),
|
||||
downloadLink.substring(downloadLink.lastIndexOf("/") + 1));
|
||||
log.debug("FileName: {}, DownloadLink: {}", fileName, downloadLink);
|
||||
if(!imageCache.containsKey(fileName)) {
|
||||
if(imageFile.exists() && imageFile.isFile()) {
|
||||
ImageChecksum imageChecksum = getImageChecksum(illustId, pageIndex);
|
||||
if(imageChecksum != null) {
|
||||
try {
|
||||
log.debug("正在检查作品Id {} 第 {} 页图片文件 {} ...", illustId, pageIndex, imageFile.getName());
|
||||
if (ImageChecksum.checkFile(imageChecksum, Files.readAllBytes(imageFile.toPath()))) {
|
||||
imageCache.put(URLs.getResourceName(downloadLink), imageFile);
|
||||
log.debug("作品Id {} 第 {} 页缓存已补充.", illustId, pageIndex);
|
||||
return getImageToBotCode(imageFile, false).toString();
|
||||
} else {
|
||||
log.warn("图片文件 {} 校验失败, 重新下载图片...", imageFile.getName());
|
||||
}
|
||||
} catch(IOException e) {
|
||||
log.error("文件检验时读取失败, 重新下载文件...(file: {})", imageFile.getPath());
|
||||
}
|
||||
} else {
|
||||
log.warn("图片存在但校验不存在, 重新下载图片...");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Throwable throwable = ImageCacheStore.executeCacheRequest(
|
||||
new ImageCacheObject(imageCache, illustId, pageIndex, downloadLink, imageFile));
|
||||
if(throwable != null) {
|
||||
throw throwable;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("图片缓存被中断", e);
|
||||
return "(错误:图片获取超时)";
|
||||
} catch (Throwable e) {
|
||||
log.error("图片 {} 获取失败:\n{}", illustId + "p" + pageIndex, Throwables.getStackTraceAsString(e));
|
||||
return "(错误: 图片获取出错)";
|
||||
}
|
||||
} else {
|
||||
log.debug("图片 {} 缓存命中.", fileName);
|
||||
}
|
||||
|
||||
return getImageToBotCode(imageCache.get(fileName), false).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过文件获取图片的BotCode代码
|
||||
* @param targetFile 图片文件
|
||||
* @param updateCache 是否刷新缓存(只是让机器人重新上传, 如果上传接口有重复检测的话是无法处理的)
|
||||
* @return 返回设定好参数的BotCode
|
||||
*/
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static BotCode getImageToBotCode(File targetFile, boolean updateCache) {
|
||||
String fileName = Objects.requireNonNull(targetFile, "targetFile is null").getName();
|
||||
BotCode code = BotCode.parse(
|
||||
CQCode.image(BotGlobal.getGlobal().getImageStoreDir().getName() + "/" + fileName));
|
||||
code.addParameter("absolutePath", targetFile.getAbsolutePath());
|
||||
code.addParameter("imageName", fileName.substring(0, fileName.lastIndexOf(".")));
|
||||
code.addParameter("updateCache", updateCache ? "true" : "false");
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取作品信息
|
||||
* @param illustId 作品Id
|
||||
* @param flushCache 强制刷新缓存
|
||||
* @return 返回作品信息
|
||||
* @throws IOException 当Http请求发生异常时抛出
|
||||
* @throws NoSuchElementException 当作品未找到时抛出
|
||||
*/
|
||||
public static JsonObject getIllustInfo(int illustId, boolean flushCache)
|
||||
throws IOException, NoSuchElementException {
|
||||
String illustIdStr = buildSyncKey(Integer.toString(illustId));
|
||||
JsonObject illustInfoObj = null;
|
||||
if (!illustInfoCache.exists(illustIdStr) || flushCache) {
|
||||
synchronized (illustIdStr) {
|
||||
if (!illustInfoCache.exists(illustIdStr) || flushCache) {
|
||||
illustInfoObj = BotGlobal.getGlobal().getPixivDownload().getIllustInfoByIllustId(illustId);
|
||||
illustInfoCache.update(illustIdStr, illustInfoObj, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(Objects.isNull(illustInfoObj)) {
|
||||
illustInfoObj = illustInfoCache.getCache(illustIdStr).getAsJsonObject();
|
||||
log.debug("作品Id {} IllustInfo缓存命中.", illustId);
|
||||
}
|
||||
return illustInfoObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取作品预加载数据.
|
||||
* 可以获取作品的一些与用户相关的信息
|
||||
* @param illustId 作品Id
|
||||
* @param flushCache 是否刷新缓存
|
||||
* @return 成功返回JsonObject对象
|
||||
* @throws IOException 当Http请求处理发生异常时抛出
|
||||
*/
|
||||
public static JsonObject getIllustPreLoadData(int illustId, boolean flushCache) throws IOException {
|
||||
String illustIdStr = buildSyncKey(Integer.toString(illustId));
|
||||
JsonObject result = null;
|
||||
if (!illustPreLoadDataCache.exists(illustIdStr) || flushCache) {
|
||||
synchronized (illustIdStr) {
|
||||
if (!illustPreLoadDataCache.exists(illustIdStr) || flushCache) {
|
||||
log.debug("IllustId {} 缓存失效, 正在更新...", illustId);
|
||||
JsonObject preLoadDataObj = BotGlobal.getGlobal().getPixivDownload()
|
||||
.getIllustPreLoadDataById(illustId)
|
||||
.getAsJsonObject("illust")
|
||||
.getAsJsonObject(Integer.toString(illustId));
|
||||
|
||||
long expire = 7200 * 1000;
|
||||
String propValue = SettingProperties.
|
||||
getProperty(SettingProperties.GLOBAL, "cache.illustPreLoadData.expire", "7200000");
|
||||
log.debug("PreLoadData有效时间设定: {}", propValue);
|
||||
try {
|
||||
expire = Long.parseLong(propValue);
|
||||
} catch (Exception e) {
|
||||
log.warn("全局配置项 \"{}\" 值非法, 已使用默认值: {}", propValue, expire);
|
||||
}
|
||||
|
||||
result = preLoadDataObj;
|
||||
illustPreLoadDataCache.update(illustIdStr, preLoadDataObj, expire);
|
||||
log.debug("作品Id {} preLoadData缓存已更新(有效时间: {})", illustId, expire);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(Objects.isNull(result)) {
|
||||
result = illustPreLoadDataCache.getCache(illustIdStr).getAsJsonObject();
|
||||
log.debug("作品Id {} PreLoadData缓存命中.", illustId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<String> getIllustPages(int illustId, PixivDownload.PageQuality quality, boolean flushCache)
|
||||
throws IOException {
|
||||
String pagesSign = buildSyncKey(Integer.toString(illustId), ".", quality.name());
|
||||
List<String> result = null;
|
||||
if (!pagesCache.exists(pagesSign) || flushCache) {
|
||||
synchronized (pagesSign) {
|
||||
if (!pagesCache.exists(pagesSign) || flushCache) {
|
||||
List<String> linkList = PixivDownload
|
||||
.getIllustAllPageDownload(BotGlobal.getGlobal().getPixivDownload().getHttpClient(),
|
||||
BotGlobal.getGlobal().getPixivDownload().getCookieStore(), illustId, quality);
|
||||
result = linkList;
|
||||
pagesCache.update(pagesSign, linkList, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(Objects.isNull(result)) {
|
||||
result = pagesCache.getCache(pagesSign);
|
||||
log.debug("作品Id {} Pages缓存命中.", illustId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private final static Random expireTimeFloatRandom = new Random();
|
||||
/**
|
||||
* 获取排行榜
|
||||
* @param contentType 排行榜类型
|
||||
* @param mode 排行榜模式
|
||||
* @param queryDate 查询时间
|
||||
* @param start 开始排名, 从1开始
|
||||
* @param range 取范围
|
||||
* @param flushCache 是否强制刷新缓存
|
||||
* @return 成功返回有值List, 失败且无异常返回空
|
||||
* @throws IOException 获取异常时抛出
|
||||
*/
|
||||
public static List<JsonObject> getRankingInfoByCache(PixivURL.RankingContentType contentType,
|
||||
PixivURL.RankingMode mode,
|
||||
Date queryDate, int start, int range, boolean flushCache)
|
||||
throws IOException {
|
||||
if(!contentType.isSupportedMode(mode)) {
|
||||
log.warn("试图获取不支持的排行榜类型已拒绝.(ContentType: {}, RankingMode: {})", contentType.name(), mode.name());
|
||||
if(log.isDebugEnabled()) {
|
||||
try {
|
||||
Thread.dumpStack();
|
||||
} catch(Exception e) {
|
||||
log.debug("本次非法请求的堆栈信息如下: \n{}", Throwables.getStackTraceAsString(e));
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
|
||||
String date = new SimpleDateFormat("yyyyMMdd").format(queryDate);
|
||||
String requestSign = buildSyncKey(contentType.name(), ".", mode.name(), ".", date);
|
||||
List<JsonObject> result = null;
|
||||
if(!rankingCache.exists(requestSign) || flushCache) {
|
||||
synchronized(requestSign) {
|
||||
if(!rankingCache.exists(requestSign) || flushCache) {
|
||||
log.debug("Ranking缓存失效, 正在更新...(RequestSign: {})", requestSign);
|
||||
List<JsonObject> rankingResult = BotGlobal.getGlobal().getPixivDownload()
|
||||
.getRanking(contentType, mode, queryDate, 1, 500);
|
||||
long expireTime = 0;
|
||||
if(rankingResult.size() == 0) {
|
||||
expireTime = 5400000 + expireTimeFloatRandom.nextInt(1800000);
|
||||
log.warn("数据获取失败, 将设置浮动有效时间以准备下次更新. (ExpireTime: {}ms)", expireTime);
|
||||
}
|
||||
result = new ArrayList<>(rankingResult).subList(start - 1, start + range - 1);
|
||||
rankingCache.update(requestSign, rankingResult, expireTime);
|
||||
log.debug("Ranking缓存更新完成.(RequestSign: {})", requestSign);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Objects.isNull(result)) {
|
||||
result = rankingCache.getCache(requestSign, start - 1, range);
|
||||
log.debug("RequestSign [{}] 缓存命中.", requestSign);
|
||||
}
|
||||
log.debug("Result-Length: {}", result.size());
|
||||
return PixivDownload.getRanking(result, start - 1, range);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取搜索结果
|
||||
* @param content 搜索内容
|
||||
* @param type 类型
|
||||
* @param area 范围
|
||||
* @param includeKeywords 包含关键词
|
||||
* @param excludeKeywords 排除关键词
|
||||
* @param contentOption 内容类型
|
||||
* @return 返回完整搜索结果
|
||||
* @throws IOException 当请求发生异常, 或接口返回异常信息时抛出.
|
||||
*/
|
||||
public static JsonObject getSearchBody(
|
||||
String content,
|
||||
String type,
|
||||
String area,
|
||||
String includeKeywords,
|
||||
String excludeKeywords,
|
||||
String contentOption) throws IOException {
|
||||
PixivSearchBuilder searchBuilder = new PixivSearchBuilder(Strings.isNullOrEmpty(content) ? "" : content);
|
||||
if (type != null) {
|
||||
try {
|
||||
searchBuilder.setSearchType(PixivSearchBuilder.SearchType.valueOf(type.toUpperCase()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("不支持的SearchType: {}", type);
|
||||
}
|
||||
}
|
||||
if (area != null) {
|
||||
try {
|
||||
searchBuilder.setSearchArea(PixivSearchBuilder.SearchArea.valueOf(area));
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("不支持的SearchArea: {}", area);
|
||||
}
|
||||
}
|
||||
if (contentOption != null) {
|
||||
try {
|
||||
searchBuilder.setSearchContentOption(PixivSearchBuilder.SearchContentOption.valueOf(contentOption));
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("不支持的SearchContentOption: {}", contentOption);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Strings.isNullOrEmpty(includeKeywords)) {
|
||||
for (String keyword : includeKeywords.split(";")) {
|
||||
searchBuilder.removeExcludeKeyword(keyword.trim());
|
||||
searchBuilder.addIncludeKeyword(keyword.trim());
|
||||
log.debug("已添加关键字: {}", keyword);
|
||||
}
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(excludeKeywords)) {
|
||||
for (String keyword : excludeKeywords.split(";")) {
|
||||
searchBuilder.removeIncludeKeyword(keyword.trim());
|
||||
searchBuilder.addExcludeKeyword(keyword.trim());
|
||||
log.debug("已添加排除关键字: {}", keyword);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("正在搜索作品, 条件: {}", searchBuilder.getSearchCondition());
|
||||
|
||||
String requestUrl = searchBuilder.buildURL().intern();
|
||||
log.debug("RequestUrl: {}", requestUrl);
|
||||
JsonObject resultBody = null;
|
||||
if(!searchBodyCache.exists(requestUrl)) {
|
||||
synchronized (requestUrl) {
|
||||
if (!searchBodyCache.exists(requestUrl)) {
|
||||
log.debug("searchBody缓存失效, 正在更新...");
|
||||
JsonObject jsonObject;
|
||||
HttpGet httpGetRequest = BotGlobal.getGlobal().getPixivDownload().
|
||||
createHttpGetRequest(requestUrl);
|
||||
HttpResponse response = BotGlobal.getGlobal().getPixivDownload().
|
||||
getHttpClient().execute(httpGetRequest);
|
||||
|
||||
String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
log.debug("ResponseBody: {}", responseBody);
|
||||
jsonObject = BotGlobal.getGlobal().getGson().fromJson(responseBody, JsonObject.class);
|
||||
|
||||
if (jsonObject.get("error").getAsBoolean()) {
|
||||
log.error("接口请求错误, 错误信息: {}", jsonObject.get("message").getAsString());
|
||||
throw new IOException("Interface Request Error: " + jsonObject.get("message").getAsString());
|
||||
}
|
||||
|
||||
long expire = 7200 * 1000;
|
||||
String propValue = SettingProperties
|
||||
.getProperty(SettingProperties.GLOBAL, "cache.searchBody.expire", "7200000");
|
||||
try {
|
||||
expire = Long.parseLong(propValue);
|
||||
} catch (Exception e) {
|
||||
log.warn("全局配置项 \"{}\" 值非法, 已使用默认值: {}", propValue, expire);
|
||||
}
|
||||
resultBody = jsonObject.getAsJsonObject().getAsJsonObject("body");
|
||||
searchBodyCache.update(requestUrl, jsonObject, expire);
|
||||
log.debug("searchBody缓存已更新(有效时间: {})", expire);
|
||||
} else {
|
||||
log.debug("搜索缓存命中.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.debug("搜索缓存命中.");
|
||||
}
|
||||
|
||||
if(Objects.isNull(resultBody)) {
|
||||
resultBody = searchBodyCache.getCache(requestUrl).getAsJsonObject().getAsJsonObject("body");
|
||||
}
|
||||
return resultBody;
|
||||
}
|
||||
|
||||
protected static ImageChecksum getImageChecksum(int illustId, int pageIndex) {
|
||||
String cacheKey = illustId + ":" + pageIndex;
|
||||
if(!imageChecksumCache.exists(cacheKey)) {
|
||||
return null;
|
||||
} else {
|
||||
return ImageChecksum.fromJsonObject(imageChecksumCache.getCache(cacheKey).getAsJsonObject());
|
||||
}
|
||||
}
|
||||
|
||||
protected static void setImageChecksum(ImageChecksum checksum) {
|
||||
String cacheKey = checksum.getIllustId() + ":" + checksum.getPage();
|
||||
imageChecksumCache.update(cacheKey, ImageChecksum.toJsonObject(checksum), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并String并存取到常量池, 以保证对象一致
|
||||
* @param keys String对象
|
||||
* @return 合并后, 如果常量池存在合并后的结果, 则返回常量池中的对象, 否则存入常量池后返回.
|
||||
*/
|
||||
private static String buildSyncKey(String... keys) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String string : keys) {
|
||||
sb.append(string);
|
||||
}
|
||||
return sb.toString().intern();
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片检验信息
|
||||
*/
|
||||
public static class ImageChecksum implements Serializable {
|
||||
|
||||
private final static MessageDigestUtils.Algorithm ALGORITHM = MessageDigestUtils.Algorithm.SHA256;
|
||||
|
||||
private ImageChecksum() {}
|
||||
|
||||
private int illustId;
|
||||
|
||||
private int page;
|
||||
|
||||
private String fileName;
|
||||
|
||||
private long size;
|
||||
|
||||
private byte[] checksum;
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public byte[] getChecksum() {
|
||||
return checksum;
|
||||
}
|
||||
|
||||
public void setChecksum(byte[] checksum) {
|
||||
this.checksum = checksum;
|
||||
}
|
||||
|
||||
public int getIllustId() {
|
||||
return illustId;
|
||||
}
|
||||
|
||||
public void setIllustId(int illustId) {
|
||||
this.illustId = illustId;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public int getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(int page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public static ImageChecksum buildImageChecksumFromStream(
|
||||
int illustId, int pageIndex,
|
||||
String fileName, InputStream imageStream) throws IOException {
|
||||
ImageChecksum checksum = new ImageChecksum();
|
||||
checksum.setIllustId(illustId);
|
||||
checksum.setPage(pageIndex);
|
||||
checksum.setFileName(fileName);
|
||||
ByteArrayOutputStream bufferStream = new ByteArrayOutputStream();
|
||||
checksum.setSize(IOUtils.copyLarge(imageStream, bufferStream));
|
||||
checksum.setChecksum(
|
||||
MessageDigestUtils.encrypt(bufferStream.toByteArray(), ALGORITHM));
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将图片检验信息转换成JsonObject
|
||||
* @param checksum 检验信息对象
|
||||
* @return 转换后的JsonObject对象
|
||||
*/
|
||||
public static JsonObject toJsonObject(ImageChecksum checksum) {
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("illustId", checksum.getIllustId());
|
||||
result.addProperty("page", checksum.getPage());
|
||||
result.addProperty("fileName", checksum.getFileName());
|
||||
result.addProperty("size", checksum.getSize());
|
||||
result.addProperty("checksum", Base64.getEncoder().encodeToString(checksum.getChecksum()));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JsonObject转换到图片检验信息
|
||||
* @param checksumObject JsonObject对象
|
||||
* @return 转换后的图片检验信息对象
|
||||
*/
|
||||
public static ImageChecksum fromJsonObject(JsonObject checksumObject) {
|
||||
ImageChecksum checksum = new ImageChecksum();
|
||||
checksum.setIllustId(checksumObject.get("illustId").getAsInt());
|
||||
checksum.setPage(checksumObject.get("page").getAsInt());
|
||||
checksum.setFileName(checksumObject.get("fileName").getAsString());
|
||||
checksum.setSize(checksumObject.get("size").getAsLong());
|
||||
checksum.setChecksum(Base64.getDecoder().decode(checksumObject.get("checksum").getAsString()));
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 比对图片文件是否完整.
|
||||
* @param checksum 图片检验信息
|
||||
* @param imageData 图片数据
|
||||
* @return 如果检验成功, 则返回true
|
||||
*/
|
||||
public static boolean checkFile(ImageChecksum checksum, byte[] imageData) {
|
||||
byte[] sha256Checksum = MessageDigestUtils.encrypt(imageData, ALGORITHM);
|
||||
return checksum.getSize() == imageData.length &&
|
||||
Arrays.equals(checksum.getChecksum(), sha256Checksum);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ImageChecksum{" +
|
||||
"illustId=" + illustId +
|
||||
", page=" + page +
|
||||
", fileName='" + fileName + '\'' +
|
||||
", size=" + size +
|
||||
", checksum=" + Base64.getEncoder().encodeToString(getChecksum()) +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ImageChecksum checksum1 = (ImageChecksum) o;
|
||||
return illustId == checksum1.illustId &&
|
||||
page == checksum1.page &&
|
||||
size == checksum1.size &&
|
||||
Objects.equals(fileName, checksum1.fileName) &&
|
||||
Arrays.equals(checksum, checksum1.checksum);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hash(illustId, page, fileName, size);
|
||||
result = 31 * result + Arrays.hashCode(checksum);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -13,9 +13,7 @@ import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@ -69,8 +67,20 @@ public class ImageCacheHandler implements EventHandler {
|
||||
}
|
||||
|
||||
log.debug("正在下载...(Content-Length: {}KB)", response.getEntity().getContentLength() / 1024);
|
||||
try(FileOutputStream fos = new FileOutputStream(storeFile)) {
|
||||
IOUtils.copy(response.getEntity().getContent(), fos);
|
||||
ByteArrayOutputStream bufferOutputStream = new ByteArrayOutputStream();
|
||||
try(FileOutputStream fileOutputStream = new FileOutputStream(storeFile)) {
|
||||
IOUtils.copy(response.getEntity().getContent(), bufferOutputStream);
|
||||
ByteArrayInputStream bufferInputStream = new ByteArrayInputStream(bufferOutputStream.toByteArray());
|
||||
CacheStoreCentral.ImageChecksum imageChecksum = CacheStoreCentral.ImageChecksum
|
||||
.buildImageChecksumFromStream(
|
||||
event.getIllustId(),
|
||||
event.getPageIndex(),
|
||||
event.getStoreFile().getName(),
|
||||
bufferInputStream
|
||||
);
|
||||
bufferInputStream.reset();
|
||||
IOUtils.copy(bufferInputStream, fileOutputStream);
|
||||
CacheStoreCentral.setImageChecksum(imageChecksum);
|
||||
} catch (IOException e) {
|
||||
log.error("下载图片时发生异常", e);
|
||||
throw e;
|
||||
|
@ -12,13 +12,16 @@ public class ImageCacheObject implements EventObject {
|
||||
|
||||
private final int illustId;
|
||||
|
||||
private final int pageIndex;
|
||||
|
||||
private final String downloadLink;
|
||||
|
||||
private final File storeFile;
|
||||
|
||||
public ImageCacheObject(Map<String, File> imageCache, int illustId, String downloadLink, File storeFile) {
|
||||
public ImageCacheObject(Map<String, File> imageCache, int illustId, int pageIndex, String downloadLink, File storeFile) {
|
||||
this.imageCache = imageCache;
|
||||
this.illustId = illustId;
|
||||
this.pageIndex = pageIndex;
|
||||
this.downloadLink = downloadLink;
|
||||
this.storeFile = storeFile;
|
||||
}
|
||||
@ -39,12 +42,17 @@ public class ImageCacheObject implements EventObject {
|
||||
return illustId;
|
||||
}
|
||||
|
||||
public int getPageIndex() {
|
||||
return pageIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ImageCacheObject that = (ImageCacheObject) o;
|
||||
return illustId == that.illustId &&
|
||||
pageIndex == that.pageIndex &&
|
||||
Objects.equals(imageCache, that.imageCache) &&
|
||||
Objects.equals(downloadLink, that.downloadLink) &&
|
||||
Objects.equals(storeFile, that.storeFile);
|
||||
@ -52,13 +60,15 @@ public class ImageCacheObject implements EventObject {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(imageCache, illustId, downloadLink, storeFile);
|
||||
return Objects.hash(imageCache, illustId, pageIndex, downloadLink, storeFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ImageCacheObject@" + Integer.toHexString(hashCode()) + "{" +
|
||||
"illustId=" + illustId +
|
||||
return "ImageCacheObject{" +
|
||||
"imageCache=" + imageCache +
|
||||
", illustId=" + illustId +
|
||||
", pageIndex=" + pageIndex +
|
||||
", downloadLink='" + downloadLink + '\'' +
|
||||
", storeFile=" + storeFile +
|
||||
'}';
|
||||
|
@ -12,6 +12,10 @@ public class ConsoleMain {
|
||||
MessageSenderBuilder.setCurrentMessageSenderFactory(new ConsoleMessageSenderFactory());
|
||||
ApplicationBoot.initialBot();
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
System.out.print("会话QQ:");
|
||||
long qqId = scanner.nextLong();
|
||||
System.out.print("会话群组号:");
|
||||
long groupId = scanner.nextLong();
|
||||
boolean isGroup = false;
|
||||
do {
|
||||
String input = scanner.nextLine();
|
||||
@ -23,7 +27,7 @@ public class ConsoleMain {
|
||||
System.out.println("System: 群模式状态已变更: " + isGroup);
|
||||
continue;
|
||||
}
|
||||
BotEventHandler.executeMessageEvent(new ConsoleMessageEvent(input, isGroup));
|
||||
BotEventHandler.executeMessageEvent(new ConsoleMessageEvent(isGroup ? groupId : 0, qqId, input));
|
||||
} while(true);
|
||||
}
|
||||
|
||||
|
@ -6,8 +6,8 @@ import java.util.Date;
|
||||
|
||||
public class ConsoleMessageEvent extends MessageEvent {
|
||||
|
||||
public ConsoleMessageEvent(String message, boolean isGroup) {
|
||||
super(isGroup ? 1 : 0, 1, message);
|
||||
public ConsoleMessageEvent(long groupId, long qqId, String message) {
|
||||
super(groupId, qqId, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2,7 +2,7 @@ package net.lamgc.cgj.bot.sort;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import net.lamgc.cgj.bot.BotCommandProcess;
|
||||
import net.lamgc.cgj.bot.cache.CacheStoreCentral;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
@ -20,6 +20,15 @@ public class PreLoadDataComparator implements Comparator<JsonElement> {
|
||||
|
||||
@Override
|
||||
public int compare(JsonElement o1, JsonElement o2) {
|
||||
if(!o1.isJsonObject() || !o2.isJsonObject()) {
|
||||
if(o1.isJsonObject()) {
|
||||
return 1;
|
||||
} else if(o2.isJsonObject()) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if(!o1.getAsJsonObject().has("illustId") || !o2.getAsJsonObject().has("illustId")) {
|
||||
if(o1.getAsJsonObject().has("illustId")) {
|
||||
return 1;
|
||||
@ -30,9 +39,13 @@ public class PreLoadDataComparator implements Comparator<JsonElement> {
|
||||
}
|
||||
}
|
||||
try {
|
||||
JsonObject illustPreLoadData1 = BotCommandProcess.getIllustPreLoadData(o1.getAsJsonObject().get("illustId").getAsInt(), false);
|
||||
JsonObject illustPreLoadData2 = BotCommandProcess.getIllustPreLoadData(o2.getAsJsonObject().get("illustId").getAsInt(), false);
|
||||
return Integer.compare(illustPreLoadData2.get(attribute.attrName).getAsInt(), illustPreLoadData1.get(attribute.attrName).getAsInt());
|
||||
JsonObject illustPreLoadData1 =
|
||||
CacheStoreCentral.getIllustPreLoadData(o1.getAsJsonObject().get("illustId").getAsInt(), false);
|
||||
JsonObject illustPreLoadData2 =
|
||||
CacheStoreCentral.getIllustPreLoadData(o2.getAsJsonObject().get("illustId").getAsInt(), false);
|
||||
return Integer.compare(
|
||||
illustPreLoadData2.get(attribute.attrName).getAsInt(),
|
||||
illustPreLoadData1.get(attribute.attrName).getAsInt());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
|
Reference in New Issue
Block a user