├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── api.php
└── core
├── config.php
└── function.php
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Dituon
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # setu-api
2 |
3 | 很久很久之前写的api, 近期在群友要求下开源
4 |
5 | 封装了 Pixiv插图/小说, 萌娘百科语音, SauceNAO/trace.moe图片搜索
6 |
7 | 基于 `php 7.0+`, 可轻易改写为`php 5.6`或更低版本
8 |
9 | # 部署
10 |
11 | 仅需原生`php 7.0+`, 无需安装第三方库
12 |
13 | # 请求
14 |
15 | 请求方式: `GET`
16 |
17 | 使用 `type` 区分搜索模式
18 |
19 | | `type` | 说明 | 参数 | 返回 | 平台 |
20 | |------------------|--------------------|------------------------------------------------------------|---------|------------------------|
21 | | `get_url_img` | 通过URL获取图片(作为反代服务器) | `url`: 图片地址 | `IMAGE` | `Pixiv` |
22 | | `get_pid_img` | 通过Pid获取图片 | `pid`: Pid ,
`page`: 页数(默认为`1`) | `IMAGE` | `Pixiv` |
23 | | `get_pid_novel` | 通过Pid获取小说(不包含系列) | `pid`: Pid ,
`page`: 页数(默认为`1`) | `JSON` | `Pixiv` |
24 | | `ranking_img` | 获取Pixiv日榜图片 | `r`: 排名 | `IMAGE` | `Pixiv` |
25 | | `random_img` | 随机色图 | `tag`: 标签,
`r18`(见下文) | `JSON` | `lolicon.app` |
26 | | `random_voice` | 随机角色语音 | `tag`: 标签 | `JSON` | `萌娘百科` |
27 | | `search_img` | 图片搜索 | `tag`: 标签,
`r`: 排名,
`r18`(见下文)
`mode`(见下文) | `JSON` | `Pixiv` |
28 | | `search_novel` | 小说搜索 | `tag`: 标签,
`r`: 排名,
`r18`(见下文)
`mode`(见下文) | `JSON` | `Pixiv` |
29 | | `img_search_img` | 以图搜图 | `url`: 图片地址 | `JSON` | `SauceNAO` `trace.moe` |
30 |
31 | ### `r18`
32 |
33 | 可以为空, 空或`0`为全年龄向, 其它数值为`R-18`模式
34 |
35 | 注意: 程序根据`Pixiv`作品`Tag`判断`r18`, 可能有误判现象
36 |
37 | ### `mode`
38 |
39 | 搜索模式(enum)
40 |
41 | | `mode` | `search_img` | `search_novel` |
42 | |----------------|-----------------------|---------------------|
43 | | `default` `0` | 按时间顺序搜索`2000`收藏数以上的作品 | 按时间顺序搜索`50`收藏数以上的作品 |
44 | | `top` `1` | 按收藏数量顺序搜索 | 按收藏数量顺序搜索 |
45 | | `enhanced` `2` | 按时间顺序搜索`100`收藏数以上的作品 | 按时间顺序搜索`1`收藏数以上的作品 |
46 |
47 | # 返回
48 |
49 | ### `IMAGE`
50 |
51 | **content-type**: `image/jpeg`
52 |
53 | 可直接作为图片处理
54 |
55 | ### `JSON`
56 |
57 | **content-type**: `application/json`
58 |
59 | * **`type`**: 类型
60 | * **`r18`**: `null` 或 `R-18` 或 `R-18G`
61 | * **`title`**: 标题
62 | * **`url`**: 资源URL, 用于音频地址, 小说封面等
63 | * **`caption`**:简介, 包含收藏数, 标签等信息
64 | * **`pid`**:Pid (`str`)
65 | * **`page`**:当前页数
66 | * **`content`**:(小说专用) 正文
67 |
68 | | `type` | 说明 | 非空字段 |
69 | |-----------------|-----------|------------------------------------------------------|
70 | | `image` | 图片 | `r18` `title` `url` `pid` `page` |
71 | | `voice` | 音频 | `url` |
72 | | `anime` | 图片搜索返回的数据 | `title` `url` `caption` |
73 | | `novel-oneshot` | 单篇小说 | `r18` `title` `url` `pid` `page` `caption` `content` |
74 | | `novel-series` | 系列小说 | `r18` `title` `url` `pid` `page` `caption` `content` |
75 |
76 | # 后话
77 |
78 | 如果此程序和您预期的一样正常工作,请给我一个 `star`
79 |
80 | 欢迎提交任何请求
81 |
82 | 交流群: `828350277`
83 |
--------------------------------------------------------------------------------
/api.php:
--------------------------------------------------------------------------------
1 | 'your cookie⭐',
5 | 'referer' => 'https://www.pixiv.net/',
6 | 'useragent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63'
7 | );
8 |
9 | public $saucenao = array(
10 | 'apikey' => 'your API key'
11 | );
12 | };
--------------------------------------------------------------------------------
/core/function.php:
--------------------------------------------------------------------------------
1 | pixiv['useragent']);
14 | curl_setopt($ch, CURLOPT_REFERER, $config->pixiv['referer']);
15 | curl_setopt($ch, CURLOPT_COOKIE, $config->pixiv['cookie']);
16 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
17 | curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
18 | $r = curl_exec($ch);
19 | curl_close($ch);
20 | return $r;
21 | }
22 |
23 | /**
24 | * 打印指定pid插图
25 | *
26 | * @param string $id int pid
27 | * @param $page int 页数, 默认为第1页
28 | * @return void
29 | */
30 | function printImageByPid(string $id, int $page = 1)
31 | {
32 | $url = "https://www.pixiv.net/ajax/illust/$id/pages";
33 | $response = sendRequest($url);
34 | $response = json_decode($response, true);
35 | $response = $response['body'];
36 | printImage($response[--$page]['urls']['regular']);
37 | }
38 |
39 | /**
40 | * 打印随机色图 **JSON**
41 | *
42 | * @param $tag string
43 | * @param $r18 bool
44 | * @return void
45 | */
46 | function printRandomImage(string $tag, bool $r18 = false)
47 | {
48 | $ch = curl_init();
49 | $durl = 'https://api.lolicon.app/setu/v2?size=regular&r18=' . $r18 ? 1 : 0 . '&tag=' . urlencode($tag);
50 | curl_setopt($ch, CURLOPT_URL, $durl);
51 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
52 | curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
53 | $rj = json_decode(curl_exec($ch), true);
54 | curl_close($ch);
55 |
56 | $imgPid = $rj['data'][0]['pid'];
57 | settype($imgPid, "string");
58 |
59 | $arr = defaultArray();
60 | $arr['type'] = 'image';
61 | $arr['pid'] = $imgPid;
62 | $arr['url'] = 'https://www.pixiv.net/artworks/' . $rj['data'][0]['pid'];
63 | $rj['data'][0]['r18'] == true && ($arr['r18'] = 'R-18');
64 | $arr['title'] = $rj['data'][0]['title'] ?? "null";
65 | $arr['page'] = '1';
66 | printJSON($arr);
67 | }
68 |
69 | /**
70 | * 打印指定排名的日榜图片
71 | *
72 | * @param $num int 作品排名
73 | * @return void
74 | */
75 | function getRankingImage(int $num)
76 | {
77 | static $updateDate, $rankingList;
78 |
79 | if ($updateDate === date('d')) {
80 | printImageByPid($rankingList[--$num]);
81 | return;
82 | }
83 |
84 | $matches = array();
85 | $json = json_decode(
86 | sendRequest('https://www.pixiv.net/ranking.php?mode=daily&content=illust&p=1&format=json'),
87 | true
88 | )["contents"];
89 |
90 | foreach ($json as $value) {
91 | $matches[] = $value['illust_id'];
92 | }
93 |
94 | $rankingList = $matches;
95 | printImageByPid($matches[--$num]);
96 | }
97 |
98 | /**
99 | * 搜索并打印小说
100 | *
101 | * @param $tag string
102 | * @param $r int 排行
103 | * @param $r18 bool
104 | * @param $searchMode string enum: top / enhanced / default
105 | * @return void
106 | */
107 | function searchNovel(string $tag, int $r, bool $r18 = false, string $searchMode = 'default')
108 | {
109 |
110 | $r18 = $r18 ? 'r18' : 'safe';
111 |
112 | switch ($searchMode) {
113 | case '1':
114 | case 'top':
115 | $order = 'popular_male_d';
116 | $blt = '';
117 | break;
118 | case '2':
119 | case 'enhanced':
120 | $order = 'date_d';
121 | $blt = '1';
122 | break;
123 | case '0':
124 | case 'default':
125 | default:
126 | $order = 'date_d';
127 | $blt = '50';
128 | break;
129 | }
130 |
131 | $page = ceil($r / 24);
132 | $r = $r - 1 - ($page - 1) * 24;
133 |
134 | $tag = urlencode($tag);
135 | $url = 'https://www.pixiv.net/ajax/search/novels/' . $tag . '?word=' . $tag . '&order=' . $order . '&mode=' . $r18 . '&blt=' . $blt . '&p=' . $page . '&s_mode=s_tag&work_lang=zh-cn&gs=1&lang=zh';
136 | $ru = json_decode(sendRequest($url), true);
137 | $data = $ru['body']['novel']['data'][$r];
138 |
139 | $novelTitle = $data['title'];
140 | $novelIsOneshot = $data['isOneshot'];
141 | $novelIsConcluded = $data['isConcluded'];
142 | $novelEpisodeCount = $data['publishedEpisodeCount'];
143 | $novelCollect = $data['bookmarkCount'];
144 | $novelTags = $data['tags'];
145 | $novelTextLength = $data['publishedTextLength'];
146 | $novelCaption = $data['caption'];
147 |
148 | if ($novelIsOneshot == 'true') {
149 | $novelState = '单篇完结';
150 | $novelEpisodeCount = '1';
151 | $novelPid = $data['novelId'];
152 | $novelURL = 'https://www.pixiv.net/novel/show.php?id=' . $novelPid;
153 | $novelType = 'novel-oneshot';
154 | } else {
155 | $novelPid = $data['id'];
156 | $novelURL = 'https://www.pixiv.net/novel/series/' . $novelPid;
157 | $novelState = '系列';
158 | if ($novelIsConcluded == 'true') {
159 | $novelState .= '完结';
160 | } else {
161 | $novelState .= '更新中';
162 | }
163 | $novelType = 'novel-series';
164 | }
165 |
166 | if ($novelCaption != '') {
167 | $novelCaption = '\n简介: ' . mb_substr($novelCaption, 0, 60, 'utf-8') . '...';
168 | }
169 |
170 | settype($novelPid, "string");
171 | $arr = defaultArray();
172 | if ($novelTags[0] == 'R-18' || $novelTags[0] == 'R-18G') {
173 | $arr['r18'] = $novelTags[0];
174 | }
175 | $novelTags = '#' . mb_substr(implode(' #', $novelTags), 0, 40, 'utf-8');
176 | $novelCaption = '标签: ' . $novelTags . '\n收藏数: ' . $novelCollect . '\n共 ' . $novelEpisodeCount . ' 话 ' . $novelTextLength . '字 ('
177 | . $novelState . ')' . $novelCaption;
178 | $arr['type'] = $novelType;
179 | $arr['pid'] = $novelPid;
180 | $arr['url'] = $novelURL;
181 | $arr['title'] = $novelTitle;
182 | $arr['page'] = '1';
183 | $arr['caption'] = $novelCaption;
184 | printJSON($arr);
185 | }
186 |
187 | /**
188 | * 搜索图片, **打印JSON**
189 | *
190 | * @param $tag string
191 | * @param $r int 排行
192 | * @param $r18 bool
193 | * @param $searchMode string enum: top / enhanced / default
194 | * @return void
195 | */
196 | function searchImage(string $tag, int $r, bool $r18 = false, string $searchMode = 'default')
197 | {
198 | $r18 = $r18 ? 'r18' : 'safe';
199 |
200 | switch ($searchMode) {
201 | case '1':
202 | case 'top':
203 | $order = 'popular_male_d';
204 | $blt = '';
205 | break;
206 | case '2':
207 | case 'enhanced':
208 | $order = 'date_d';
209 | $blt = '100';
210 | break;
211 | case '0':
212 | case 'default':
213 | default:
214 | $order = 'date_d';
215 | $blt = '2000';
216 | break;
217 | }
218 |
219 | $page = ceil($r / 60);
220 | $r = $r - 1 - ($page - 1) * 60;
221 |
222 | $tag = urlencode($tag);
223 | $url = 'https://www.pixiv.net/ajax/search/artworks/' . $tag . '?word=' . $tag . '&order=' . $order . '&mode=' . $r18 . '&blt=' . $blt . '&p=' . $page . '&s_mode=s_tag&type=all&lang=zh';
224 | $ru = json_decode(sendRequest($url), true);
225 | $imgPid = $ru['body']['illustManga']['data'][$r]['id'];
226 |
227 | $imgTitle = $ru['body']['illustManga']['data'][$r]['title'];
228 | $imgTags = $ru['body']['illustManga']['data'][$r]['tags'];
229 | $arr = defaultArray();
230 |
231 | if ($imgTags[0] === 'R-18' || $imgTags[0] === 'R-18G') {
232 | $arr['r18'] = $imgTags[0];
233 | }
234 | settype($imgPid, "string");
235 | $arr['type'] = 'image';
236 | $arr['pid'] = $imgPid;
237 | $arr['url'] = 'https://www.pixiv.net/artworks/' . $imgPid;
238 | $arr['title'] = $imgTitle;
239 | $arr['page'] = '1';
240 | printJSON($arr);
241 | }
242 |
243 | /**
244 | * 打印小说
245 | *
246 | * @param $pid string
247 | * @return void
248 | */
249 | function printNovel(string $pid)
250 | {
251 | $html = file_get_contents("https://www.pixiv.net/novel/show.php?id=" . $pid);
252 | $titlere = '/(?<=title\":\").*?(?=\",\"likeCount)/';
253 | $captionre = '/(?<=description\":\").*?(?=\",\"id)/';
254 | $contentre = '/(?<=content\":\").*?(?=\",\"coverUrl)/';
255 | preg_match($titlere, $html, $title);
256 | preg_match($captionre, $html, $caption);
257 | preg_match($contentre, $html, $content);
258 |
259 | $arr = defaultArray();
260 | if (strpos($html, '"tag":"R-18"')) {
261 | $arr['r18'] = 'R-18';
262 | } else if (strpos($html[0], '"tag":"R-18G"')) {
263 | $arr['r18'] = 'R-18G';
264 | }
265 | $arr['type'] = 'novel-oneshot';
266 | $arr['pid'] = $pid;
267 | $arr['title'] = $title[0];
268 | $arr['page'] = '1';
269 | $arr['caption'] = $caption[0];
270 | $arr['content'] = str_replace("[newpage]", "", $content[0]);
271 | printJSON($arr);
272 | }
273 |
274 | /**
275 | * 打印音频 **JSON**
276 | *
277 | * @param $tag string
278 | * @return void
279 | */
280 | function getVoice(string $tag)
281 | {
282 | $opts = ['http' => ['header' => "Referer: mzh.moegirl.org.cn"]];
283 | $context = stream_context_create($opts);
284 |
285 | $url = 'https://mzh.moegirl.org.cn/api.php?action=query&format=json&prop=pageprops&generator=prefixsearch&ppprop=displaytitle&gpssearch=' . urlencode($tag);
286 | $json = json_decode(file_get_contents($url, false, $context), true);
287 |
288 | $arr = defaultArray();
289 | $arr['type'] = 'voice';
290 |
291 | if ($json['query']['pages'] == NULL) {
292 | $arr['url'] = 'null';
293 | printJSON($arr);
294 | return;
295 | }
296 |
297 | $title = array_shift($json['query']['pages'])['title'];
298 |
299 | $url = 'https://mzh.moegirl.org.cn/api.php?action=query&format=json&imlimit=100&prop=images&titles=' . urlencode($title);
300 | $json = json_decode(file_get_contents($url, false, $context), true);
301 |
302 | $resource = array_shift($json['query']['pages'])['images'];
303 | if ($resource == null) {
304 | $arr['url'] = 'null';
305 | printJSON($arr);
306 | return;
307 | }
308 |
309 | $voices = null;
310 |
311 | foreach ($resource as $r) {
312 | $end = substr($r['title'], -4);
313 | if ($end === '.mp3' || $end === '.ogg') {
314 | $voices[] = $r['title'];
315 | }
316 | }
317 |
318 | if (!$voices) {
319 | $arr['url'] = 'null';
320 | printJSON($arr);
321 | return;
322 | }
323 | is_array($voices) ? $res = $voices[random_int(0, sizeof($voices) - 1)] : $res = $voices;
324 |
325 | $url = 'https://mzh.moegirl.org.cn/api.php?action=query&format=json&prop=videoinfo&viprop=url&titles=' . urlencode($res);
326 | $json = json_decode(file_get_contents($url, false, $context), true);
327 |
328 | $arr['url'] = $json['query']['pages']['-1']['videoinfo'][0]['url'];
329 | printJSON($arr);
330 | }
331 |
332 | /**
333 | * 以图搜图, **打印JSON**
334 | *
335 | * @param string $url
336 | * @return void
337 | */
338 | function searchImageByImage(string $url)
339 | {
340 | global $config;
341 |
342 | $arr = defaultArray();
343 | $arr['type'] = 'anime';
344 |
345 | $stream_opts = [
346 | "ssl" => [
347 | "verify_peer" => false,
348 | "verify_peer_name" => false,
349 | ]
350 | ];
351 |
352 | $apikey = $config->saucenao['apikey'];
353 | $apiurl = "https://saucenao.com/search.php?db=999&output_type=2&testmode=1&numres=1&api_key=$apikey&url=$url";
354 | $json = json_decode(file_get_contents($apiurl, false, stream_context_create($stream_opts)), true);
355 | $similarity = number_format($json['results'][0]['header']['similarity'], 2);
356 | if ($similarity >= 80) { //saucenao
357 | $arr['title'] = '数据来源: SauceNAO (' . $similarity . '%)';
358 | $arr['url'] = $json['results'][0]['header']['thumbnail'];
359 | $arr['caption'] = $json['results'][0]['data']['jp_name'] ?? '';
360 | $arr['caption'] .= $json['data']['title_japanese'] ?? '';
361 | $arr['caption'] .= $json['data']['title'] ?? '';
362 | if (isset($json['data']['pixiv_id'])) {
363 | $arr['caption'] .= "\n" . $json['data']['pixiv_id'];
364 | }
365 | $ext_urls = $json['results'][0]['data']['ext_urls'][0];
366 | if (isset($ext_urls)) {
367 | $arr['caption'] .= '链接: ' . $ext_urls;
368 | }
369 | if (isset($json['results'][0]['data']['mal_id'])) {
370 | $arr['caption'] .= "\n第 " . $json['results'][0]['data']['part'] . ' 话' ?? '';
371 | $est_time = $json['results'][0]['data']['est_time'];
372 | if (isset($est_time)) {
373 | $arr['caption'] .= substr($est_time, 0, strpos($est_time, ' /'));
374 | }
375 | // $url = "https://api.jikan.moe/v4/anime/" . $json['results'][0]['data']['mal_id'];
376 | // $json = json_decode(file_get_contents($url, false, stream_context_create($stream_opts)), true);
377 | }
378 | } else { //trace.moe
379 | $apiurl = "https://api.trace.moe/search?url=$url";
380 | $json = json_decode(file_get_contents($apiurl, false, stream_context_create($stream_opts)), true);
381 | $similarity = number_format($json['result'][0]['similarity'] * 100, 2);
382 | if ($similarity >= 80) {
383 | $arr['title'] = '数据来源: trace.moe (' . $similarity . '%)';
384 | $arr['url'] = $json['result'][0]['image'];
385 | $arr['caption'] = '链接: ' . $json['result'][0]['anilist'];
386 | }
387 | }
388 |
389 | printJSON($arr);
390 | }
391 |
392 | /**
393 | * 将修改后的默认数组打印为json
394 | *
395 | * @param $arr array
396 | * @return void
397 | */
398 | function printJSON(array $arr)
399 | {
400 | @header('content-type: application/json');
401 | echo (json_encode($arr));
402 | }
403 |
404 | /**
405 | * 打印一张网络图片
406 | *
407 | * @param $url string 图片地址
408 | * @return void
409 | */
410 | function printImage(string $url)
411 | {
412 | $ImageData = sendRequest($url);
413 | @header('content-type: image/jpeg');
414 | echo ($ImageData);
415 | }
416 |
417 | /**
418 | * 获取默认数组
419 | *
420 | * @return array
421 | */
422 | function defaultArray(): array
423 | {
424 | return array_fill_keys(array('type', 'pid', 'url', 'r18', 'title', 'page', 'caption', 'content'), '');
425 | }
--------------------------------------------------------------------------------