├── .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 | } --------------------------------------------------------------------------------