Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
BiliComicWebReader
shit title placeholder
<?php
if (empty($_GET['mangaid']) || !preg_match('(^\d+$)', $_GET['mangaid'])) {
errordie('无效id');
}
chdir(__DIR__);
$idxDb = new PDO('sqlite:index.db');
$detail = cget('http://manga.bilibili.com/twirp/comic.v2.Comic/ComicDetail', [
'post' => buildParam([
'access_key'=>$_COOKIE['access_key'],
'actionKey'=>'appkey',
'appkey'=>$appkey,
'build'=>APPBUILD,
'comic_id'=>$_GET['mangaid'],
'device'=>'phone',
'mobi_app'=>'iphone_comic',
'platform'=>'ios',
'ts'=>time(),
'version'=>APPVER
], $appsecret)
]);
$detail = json_decode($detail, true);
if ($detail['code'] !== 0) {
errordie('获取出错: ['.$detail['code'].']'.$detail['msg']);
}
$detail = $detail['data'];
usort($detail['ep_list'], function ($a, $b) {return $a['ord'] - $b['ord'];});
$store_stmt = $idxDb->prepare('REPLACE INTO index_data (epid,mangaid,hash,data) VALUES (?,?,?,CAST(? AS BLOB))');
$history = $idxDb->prepare('INSERT OR IGNORE INTO index_data_history SELECT *,? as time FROM index_data WHERE epid=?');
$hashChk = $idxDb->prepare('SELECT count(epid) FROM index_data WHERE epid=? AND hash=?');
$epChk = $idxDb->prepare('SELECT count(epid) FROM index_data WHERE epid=?');
if ($detail['discount_type'] == 2 && $detail['discount'] == 0) {
$purchasedEpList = $detail['ep_list'];
} else {
$purchasedEpList = array_filter($detail['ep_list'], function ($i) {return !$i['is_locked'];});
}
?>
<!DOCTYPE html><html>
<head>
<meta charset="UTF-8" name="viewport" content="width=device-width,user-scalable=no">
<meta name="format-detection" content="telephone=no" />
<meta name="referrer" content="never">
<title><?php echo $detail['title'];?> - 书籍 - bilibili漫画 阅读器 - BiliPlus</title>
<script src="materialize.min.js"></script>
<link rel="stylesheet" href="materialize.min.css" />
<style>
body {width:95%;max-width:700px;margin:5px auto;background:#EFEFF4;cursor:default}
</style>
</head>
<body>
<div class="col s12">
<h3><?php echo $detail['title'];?></h3>
<p>共计 <?php echo count($detail['ep_list'])?> 话,可访问共 <?php echo count($purchasedEpList)?> 话</p>
<p>
<?php
ob_flush();
flush();
$i = 0;
$total = count($purchasedEpList);
set_time_limit(0);
$apiCurl = curl_init();
$idxCurl = curl_init();
foreach ($purchasedEpList as $ep) {
$i++;
if (isset($_GET['skip_cached'])) {
$epChk->execute([$ep['id']]);
if ($epChk->fetch()[0] == 1) continue;
}
echo "\n <div>".$i.'/'.$total.' '.$ep['id'].' '.$ep['short_title'].'...';
$idxUrl = json_decode(cget('http://manga.bilibili.com/twirp/comic.v1.Comic/GetImageIndex', [
'post' => buildParam([
'access_key'=>$_COOKIE['access_key'],
'actionKey'=>'appkey',
'appkey'=>$appkey,
'build'=>APPBUILD,
'device'=>'phone',
'ep_id'=>$ep['id'],
'mobi_app'=>'iphone_comic',
'platform'=>'ios',
'ts'=>time(),
'version'=>APPVER,
], $appsecret),
'curl'=>$apiCurl
]), true);
if ($idxUrl['code'] !== 0) {
echo '获取索引出错: ['.$idxUrl['code'].']'.$idxUrl['msg'].'</div>';
continue;
}
$encryptedIndexData = cget('http://i0.hdslb.com' . $idxUrl['data']['path'], ['curl'=>$idxCurl]);
preg_match('(/manga/(\d+)/(\d+)/data\.index)', $idxUrl['data']['path'], $idMatch);
$realmangaid = $idMatch[1];
$realepid = $idMatch[2];
$indexDataStr = decryptBiliComicIndex($encryptedIndexData, $realmangaid, $realepid);
if (!$indexDataStr) {
echo '下载索引出错</div>';
continue;
}
$indexData = @json_decode($indexDataStr, true);
if ($indexData == NULL) {
echo '下载索引出错</div>';
continue;
}
$data = brotli_compress($indexDataStr, 9);
$hash = crc32($data);
$hashChk->execute([$ep['id'], $hash]);
$updated = $hashChk->fetch()[0] != 1;
do {
try {
$idxDb->beginTransaction();
break;
} catch (Exception $e) {}
} while (1);
$store_stmt->execute([$realepid, $realmangaid, $hash, $data]);
$history->execute([time(), $ep['id']]);
$idxDb->commit();
if ($updated) echo '更新'.$idxUrl['data']['last_modified'];
echo "完成</div>";
@ob_flush();@flush();
}
?>
</p>
<p><a href="javascript:window.close()" class="col s4 waves-effect waves-light btn">关闭</a></p>
</div>
</body>
</html>
<?php
function decryptBiliComicIndex($data, $mangaid, $epid) {
if (substr($data, 0, 9) !== 'BILICOMIC') {
return false;
}
$body = substr($data, 9);
$size = strlen($body);
$out = str_repeat("\0", $size);
$key = [
$epid & 0xff,
$epid >> 8 & 0xff,
$epid >> 16 & 0xff,
$epid >> 24 & 0xff,
$mangaid & 0xff,
$mangaid >> 8 & 0xff,
$mangaid >> 16 & 0xff,
$mangaid >> 24 & 0xff
];
for ($i = 0; $i < $size; $i++) {
$byte = ord($body[$i]);
$byte ^= ($key[($i) % 8]);
$out[$i] = chr($byte);
}
return readBiliComicIndexZip(new MemoryStream($out));
}
function readBiliComicIndexZip(MemoryStream $out) {
$out->littleEndian = true;
$out->position = $out->size - 22;
$signature = $out->readData(4);
$num = $out->short;
$start = $out->short;
$numRecords = $out->short;
$total = $out->short;
$cdsize = $out->long;
$cdoffset = $out->long;
$commentlen = $out->short;
$out->position = $cdoffset + 4 + 2 * 6;
$crc = $out->ulong;
$compressedSize = $out->long;
$uncompressedSize = $out->long;
$out->position = 26;
$fnameLen = $out->short;
$extraLen = $out->short;
$out->position += $fnameLen + $extraLen;
$compressedData = $out->readData($compressedSize);
$data = gzinflate($compressedData);
if (crc32($data) !== $crc) {
return false;
}
return $data;
}
abstract class Stream {
abstract protected function read($length);
abstract public function seek($position);
abstract public function position();
abstract protected function getPos();
abstract protected function setPos($pos);
public function __get($name) {
switch($name) {
case 'position': return $this->getPos();
case 'bool': return $this->readBoolean();
case 'byte': return $this->readData(1);
case 'short': return $this->readInt16();
case 'ushort': return $this->readUint16();
case 'long': return $this->readInt32();
case 'ulong': return $this->readUint32();
case 'longlong': return $this->readInt64();
case 'ulonglong': return $this->readUint64();
case 'float': return $this->readFloat();
case 'double': return $this->readDouble();
case 'string': return $this->readStringToNull();
case 'line': return $this->readStringToReturn();
default: throw new Exception("Access undefined field ${name} of class ".get_class($this));
}
}
public function __set($name, $val) {
switch($name) {
case 'position': return $this->setPos($val);
default: throw new Exception("Assign value to undefined field ${name} of class ".get_class($this));
}
}
public $littleEndian = false;
public $size;
public function readStringToNull() {
$s = '';
while (ord($char = $this->read(1)) != 0) {
$s .= $char;
}
return $s;
}
public function readStringAt($pos) {
$current = $this->position;
$this->position = $pos;
$data = $this->string;
$this->position = $current;
return $data;
}
public function readStringToReturn() {
$s = '';
while ($this->position < $this->size && ($char = $this->read(1)) != "\n") {
$s .= $char;
}
return trim($s,"\r");
}
public function readBoolean() {
return ord($this->byte)>0;
}
public function readInt16() {
$uint = $this->readUint16();
$sint = unpack('s', pack('S', $uint))[1];
return $sint;
}
public function readUint16() {
$int = $this->read(2);
if (strlen($int) != 2) return 0;
return unpack($this->littleEndian?'v':'n', $int)[1];
}
public function readInt32() {
$uint = $this->readUint32();
$sint = unpack('l', pack('L', $uint))[1];
return $sint;
}
public function readUint32() {
$int = $this->read(4);
if (strlen($int) != 4) return 0;
return unpack($this->littleEndian?'V':'N', $int)[1];
}
public function readInt64() {
$uint = $this->readUint64();
$sint = unpack('q', pack('Q', $uint))[1];
return $sint;
}
public function readUint64() {
$int = $this->read(8);
if (strlen($int) != 8) return 0;
return unpack($this->littleEndian?'P':'J', $int)[1];
}
public function readFloat() {
$int = $this->read(4);
if (strlen($int) != 4) return 0;
if (!$this->littleEndian) $int = $int[3].$int[2].$int[1].$int[0];
return unpack(/*$this->littleEndian?'g':'G'*/ 'f', $int)[1];
}
public function readDouble() {
$int = $this->read(8);
if (strlen($int) != 8) return 0;
if (!$this->littleEndian) $int = $int[7].$int[6].$int[5].$int[4].$int[3].$int[2].$int[1].$int[0];
return unpack(/*$this->littleEndian?'e':'E'*/ 'd', $int)[1];
}
public function readData($size) {
if ($size <= 0) return '';
return $this->read($size);
}
public function readDataAt($pos, $size) {
$current = $this->position;
$this->position = $pos;
$data = $this->readData($size);
$this->position = $current;
return $data;
}
public function alignStream($alignment) {
$mod = $this->position % $alignment;
if ($mod != 0) {
$this->position += $alignment - $mod;
}
}
public function readAlignedString($len) {
$string = $this->readData($len);
$this->alignStream(4);
return $string;
}
}
class MemoryStream extends Stream {
private $data;
private $offset;
function __construct($data) {
$this->data = $data;
$this->size = strlen($data);
}
function __destruct() {
$this->data = NULL;
}
protected function read($length) {
$data = substr($this->data, $this->offset, $length);
$this->offset += $length;
return $data;
}
public function seek($position) {
$this->offset = $position;
}
public function write($newData) {
$this->data .= $newData;
$this->size += strlen($newData);
}
public function position() {
return $this->offset;
}
protected function getPos() {
return $this->offset;
}
protected function setPos($pos) {
$this->offset = $pos;
}
}
<?php
if (empty($_GET['mangaid']) || !preg_match('(^\d+$)', $_GET['mangaid'])) {
errordie('无效id');
}
chdir(__DIR__);
$idxDb = new PDO('sqlite:index.db');
$detail = cget('http://manga.bilibili.com/twirp/comic.v2.Comic/ComicDetail', [
'post' => buildParam([
'access_key'=>$_COOKIE['access_key'],
'actionKey'=>'appkey',
'appkey'=>$appkey,
'build'=>APPBUILD,
'comic_id'=>$_GET['mangaid'],
'device'=>'phone',
'mobi_app'=>'iphone_comic',
'platform'=>'ios',
'ts'=>time(),
'version'=>APPVER
], $appsecret)
]);
if (isset($_GET['api'])) {
header('Content-Type: application/json; charset="UTF-8"');
echo $detail; exit;
}
$detail = json_decode($detail, true);
if ($detail['code'] !== 0) {
errordie('获取出错: ['.$detail['code'].']'.$detail['msg']);
}
$detail = $detail['data'];
usort($detail['ep_list'], function ($a, $b) {return $a['ord'] > $b['ord'] ? 1 : -1;});
if ($detail['discount_type'] == 2 && $detail['discount'] == 0) {
foreach ($detail['ep_list'] as &$ep) {
$ep['is_locked'] = false;
}
unset($ep);
}
$idxDb->prepare('REPLACE INTO manga_info (id,title,list) VALUES (?,?,CAST(? AS BLOB))')->execute([
$detail['id'],
$detail['title'],
brotli_compress(json_encode($detail['ep_list'], JSON_UNESCAPED_UNICODE+JSON_UNESCAPED_SLASHES), 9)
]);
?>
<!DOCTYPE html><html>
<head>
<meta charset="UTF-8" name="viewport" content="width=device-width,user-scalable=no">
<meta name="format-detection" content="telephone=no" />
<meta name="referrer" content="never">
<title><?php echo $detail['title'];?> - 书籍 - bilibili漫画 阅读器 - BiliPlus</title>
<script src="materialize.min.js"></script>
<link rel="stylesheet" href="materialize.min.css" />
<style>
body {width:95%;max-width:700px;margin:5px auto;background:#EFEFF4;cursor:default}
.comic-cover{height:200px}
.episode{display:inline-block;width:50px;margin:8px 15px;border:1px solid;border-radius:5px;text-align:center;}
.episode.locked{color:#666;border-color:#666}
a.episode:visited{color:#0645ad}
rt{font-size:70%;color:#888}
.multiple-version{position:relative}
.multiple-version::after{content:"*";position:absolute}
</style>
</head>
<body>
<div class="col s12">
<h3><?php echo $detail['title'];?></h3>
<div style="float:right"><?php echo implode('', $detail['styles']);?></div>
<h5><?php echo implode(' ', $detail['author_name']);?></h5>
<div style="float:right"><img style="height:200px" alt="封面" src="<?php echo str_replace('http:','',$detail['vertical_cover']);?>@400h.jpg"><div style="text-align:center"><a href="<?php echo $detail['horizontal_cover'];?>" target="_blank">横封</a> <a href="<?php echo $detail['vertical_cover'];?>" target="_blank">竖封</a> <a href="<?php echo $detail['square_cover'];?>" target="_blank">方封</a></div></div>
<p style="white-space:pre-wrap"><?php echo $detail['evaluate'];?></p>
<p>上次阅读:<?php echo $detail['read_short_title'];?></p>
<p><?php echo $detail['renewal_time'];?></p>
<?php if ($detail['discount_type']) {
?>
<p>进行中的优惠:<?php echo ([1=>'全本',2=>'单章'][$detail['discount_type']]?:('类型'.$detail['discount_type'].' ')).($detail['discount']/10) ?>折 | <?php echo $detail['discount_end']?></p>
<?php
}?>
<p><a href="/html/reply.htm#type=22&id=<?php echo $_GET['mangaid']?>&title=<?php echo rawurlencode($detail['title'])?>" target="_blank">评论区</a><span class="review"></span> | <a href="?act=batch_index&mangaid=<?php echo $_GET['mangaid']?>" target="_blank">刷新全部索引</a> | <a href="?act=batch_index&skip_cached&mangaid=<?php echo $_GET['mangaid']?>" target="_blank">获取未缓存索引</a> | <a href="?act=detail_preview&mangaid=<?php echo $_GET['mangaid']?>" target="_manga_view">章节预览</a></p>
<p>&copy; Copyright <a href="https://manga.bilibili.com/m/detail/mc<?php echo $_GET['mangaid']?>" target="_blank">bilibili</a></p>
<div style="clear:both"></div>
<div style="text-align:center"><!--
<?php
$idxCntStmt = $idxDb->prepare('SELECT count(hash) FROM index_data_history WHERE epid=?');
foreach ($detail['ep_list'] as $ep) {
$idxCntStmt->execute([$ep['id']]);
$idxCnt = $idxCntStmt->fetch()[0];
?>
|--><ruby><a class="episode<?php if ($ep['is_locked']) echo ' locked'; if ($idxCnt>1) echo ' multiple-version'?>" target="_manga_view" href="?act=read&mangaid=<?php echo $detail['id'];?>&epid=<?php echo $ep['id'];?>"><?php echo $ep['short_title'];?></a><rt><?php echo $ep['id'];?></rt></ruby><!--
<?php
}
?>
|--></div>
</div>
<script>function review_count(data){if(data.code==0){[].slice.call(document.getElementsByClassName('review')).forEach(function(i){i.textContent=''+data.data.count+'';})}}</script>
<script src="https://api.bilibili.com/x/reply/count?oid=<?php echo $_GET['mangaid'];?>&type=22&jsonp=jsonp&callback=review_count&_=<?php echo time();?>" async></script>
</body>
</html>
<?php
if (empty($_GET['mangaid']) || !preg_match('(^\d+$)', $_GET['mangaid'])) {
errordie('无效id');
}
chdir(__DIR__);
$idxDb = new PDO('sqlite:index.db');
$mangaInfoStmt = $idxDb->prepare('SELECT * FROM manga_info WHERE id=?');
$mangaInfoStmt->execute([$_GET['mangaid']]);
$mangaInfo = $mangaInfoStmt->fetch(PDO::FETCH_ASSOC);
if (empty($mangaInfo)) {
header('Location: ?act=detail&mangaid='.$_GET['mangaid'], true, 302);
exit;
}
$mangaTitle = $mangaInfo['title'];
$epList = json_decode(brotli_uncompress($mangaInfo['list']), true);
usort($epList, function ($a, $b) {return $a['ord'] > $b['ord'] ? 1 : -1;});
$epIdxStmt = $idxDb->prepare('SELECT data FROM index_data WHERE epid=?');
$imgUrl = [];
foreach ($epList as &$ep) {
$epIdxStmt->execute([$ep['id']]);
$epIdx = $epIdxStmt->fetch();
if (empty($epIdx)) continue;
$epIdx = json_decode(brotli_uncompress($epIdx['data']), true);
$idx = 0;
if (in_array($epIdx['pics'][$idx], ['/bfs/manga/dc7914d65771003337d24be6281c6189934b89c0.jpg', '/bfs/manga/e06419ed685fab1df07134fc4e0f2e9011808cb5.jpg'])) $idx++;
$img = getImgUrl($epIdx, $idx, 150);
$ep['first_image'] = $img;
$imgUrl[] = $img['url'];
}
unset($ep);
function getImgUrl(&$indexData, $i, $setWidth) {
$append = '@'.($setWidth*2).'w.jpg';
if ($indexData['sizes'][$i]['cx'] <= $setWidth * 2) {
$append = '@.jpg';
}
return [
'url' => 'https://i0.hdslb.com'.$indexData['pics'][$i].$append,
'size' => [
$setWidth,
$setWidth / $indexData['sizes'][$i]['cx'] * $indexData['sizes'][$i]['cy']
]
];
}
$tokens = empty($imgUrl) ? ['code'=>0,'data'=>[]]: json_decode(cget('http://manga.bilibili.com/twirp/comic.v1.Comic/ImageToken', [
'post' => buildParam([
'access_key'=>$_COOKIE['access_key'],
'actionKey'=>'appkey',
'appkey'=>$appkey,
'build'=>APPBUILD,
'device'=>'phone',
'mobi_app'=>'iphone_comic',
'platform'=>'ios',
'ts'=>time(),
'urls'=>json_encode($imgUrl),
'version'=>APPVER,
], $appsecret)
]), true);
if (!$tokens || $tokens['code'] != 0) {
errordie('获取凭据出错: ['.$tokens['code'].']'.$tokens['msg']);
}
$tokenMap = [];
foreach ($tokens['data'] as $item) {
$tokenMap[$item['url']] = $item['token'];
}
?>
<!DOCTYPE html><html>
<head>
<meta charset="UTF-8" name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no" />
<meta name="referrer" content="never">
<title>章节预览 - <?php echo $mangaInfo['title'];?> - 书籍 - bilibili漫画 阅读器 - BiliPlus</title>
<script src="materialize.min.js"></script>
<link rel="stylesheet" href="materialize.min.css" />
<style>
body {width:95%;max-width:700px;margin:5px auto;background:#EFEFF4;cursor:default}
.episode-item{margin:5px 3px;display:inline-block}
.episode-item img{width:150px}
rt{font-size:100%;color:#888}
</style>
</head>
<body>
<div class="col s12">
<h3><?php echo $mangaInfo['title'];?></h3>
<p>&copy; Copyright <a href="https://manga.bilibili.com/m/detail/mc<?php echo $_GET['mangaid']?>" target="_blank">bilibili</a></p>
<div style="clear:both"></div>
<div style="text-align:center"><!--
<?php
foreach ($epList as $ep) {
if (isset($ep['first_image'])) {
$imgUrl = $ep['first_image']['url'];
$attr = 'src="'.$imgUrl.'?token='.$tokenMap[$imgUrl].'" width="'.$ep['first_image']['size'][0].'" height="'.$ep['first_image']['size'][1].'"';
} else {
$attr = 'src="about:blank"';
}
?>
|--><div class="episode-item"><a href="?act=read&mangaid=<?php echo $mangaInfo['id'];?>&epid=<?php echo $ep['id'];?>"><img <?php echo $attr?>></a><br><?php echo $ep['short_title'].' ('.$ep['id'];?>)</div><!--
<?php
}
print_r($epList);
?>
|--></div>
</div>
</body>
</html>
<?php
if (empty($_GET['epid']) || !preg_match('(^\d+$)', $_GET['epid'])) {
errordie('无效id');
}
chdir(__DIR__);
$idxDb = new PDO('sqlite:index.db');
$result = $idxDb->prepare('SELECT * FROM index_data_history WHERE epid=?');
$result->execute([$_GET['epid']]);
$resultArr = array_map(function ($i) {$i['data'] = json_decode(brotli_uncompress($i['data']), true);return $i;}, $result->fetchAll(PDO::FETCH_ASSOC));
usort($resultArr, function ($a, $b) {return $a['time'] - $b['time'];});
$diff = [];
$imgInfo = [];
for ($i=0; $i<count($resultArr) - 1; $i++) {
$subdiff = [];
$len = max(count($resultArr[$i]['data']['pics']), count($resultArr[$i+1]['data']['pics']));
for ($j=0; $j<$len; $j++) {
if ($resultArr[$i]['data']['pics'][$j] != $resultArr[$i+1]['data']['pics'][$j]) {
$imgInfo[ $resultArr[$i]['data']['pics'][$j] ] = $resultArr[$i]['data']['sizes'][$j];
$imgInfo[ $resultArr[$i+1]['data']['pics'][$j] ] = $resultArr[$i+1]['data']['sizes'][$j];
$subdiff[] = [
$j + 1,
$resultArr[$i]['data']['pics'][$j],
$resultArr[$i+1]['data']['pics'][$j]
];
}
}
$diff[] = [
'hash' => [dechex($resultArr[$i]['hash']), dechex($resultArr[$i+1]['hash'])],
'time' => [$resultArr[$i]['time'], $resultArr[$i+1]['time']],
'diff' => $subdiff
];
}
unset($imgInfo['']);
$setWidth = 400;
foreach (array_keys($imgInfo) as $img) {
$size = $imgInfo[$img];
$append = '@'.($setWidth*2).'w.jpg';
if ($size['cx'] <= $setWidth * 2) {
$append = '@.jpg';
}
$url = 'https://i0.hdslb.com'.$img.$append;
$imgInfo[$img] = [
'cx' => $setWidth,
'cy' => $setWidth / $size['cx'] * $size['cy'],
'url' => $url
];
}
$imgUrl = array_values(array_map(function ($i){return $i['url'];}, $imgInfo));
$tokens = empty($imgUrl) ? ['code'=>0,'data'=>[]]: json_decode(cget('http://manga.bilibili.com/twirp/comic.v1.Comic/ImageToken', [
'post' => buildParam([
'access_key'=>$_COOKIE['access_key'],
'actionKey'=>'appkey',
'appkey'=>$appkey,
'build'=>APPBUILD,
'device'=>'phone',
'mobi_app'=>'iphone_comic',
'platform'=>'ios',
'ts'=>time(),
'urls'=>json_encode($imgUrl),
'version'=>APPVER,
], $appsecret)
]), true);
if (!$tokens || $tokens['code'] != 0) {
errordie('获取凭据出错: ['.$tokens['code'].']'.$tokens['msg']);
}
$tokenMap = [];
foreach ($tokens['data'] as $item) {
$tokenMap[$item['url']] = $item['token'];
}
?>
<!DOCTYPE html><html>
<head>
<meta charset="UTF-8" name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no" />
<meta name="referrer" content="never">
<title>章节索引历史 - bilibili漫画 阅读器 - BiliPlus</title>
<script src="materialize.min.js"></script>
<link rel="stylesheet" href="materialize.min.css" />
<style>
body {width:800px;margin:5px auto;background:#EFEFF4;cursor:default;text-align:center}
span.col {display:inline-block}
.row img.col{padding:0}
</style>
</head>
<body>
<div class="row s12">
<?php
foreach ($diff as $subdiff) {
?>
<hr>
<span class="col s6"><?php echo date('Y/m/d H:i:s', $subdiff['time'][0])?><br><?php echo $subdiff['hash'][0]?></span>
<span class="col s6"><?php echo date('Y/m/d H:i:s', $subdiff['time'][1])?><br><?php echo $subdiff['hash'][1]?></span>
<?php
foreach ($subdiff['diff'] as $diffitem) {
?>
<p>P<?php echo $diffitem[0]?></p>
<img class="col s6" width="<?php echo $imgInfo[$diffitem[1]]['cx']?>" height="<?php echo $imgInfo[$diffitem[1]]['cy']?>" _src="<?php $url = $imgInfo[$diffitem[1]]['url']; echo $url.'?token='.$tokenMap[$url]?>"><!--
|--><img class="col s6" width="<?php echo $imgInfo[$diffitem[2]]['cx']?>" height="<?php echo $imgInfo[$diffitem[2]]['cy']?>" _src="<?php $url = $imgInfo[$diffitem[2]]['url']; echo $url.'?token='.$tokenMap[$url]?>">
<?php
}
}
?>
</div>
<script>
window.img_lazyload=function(){var t=[].slice.call(document.getElementsByTagName("img"));t.forEach(function(t){if(t.hasAttribute("_src")){var e=t.getBoundingClientRect();e.bottom<-100||e.top>innerHeight+1000||e.right<-1000||e.left>innerWidth+100||(t.setAttribute("src",t.getAttribute("_src")),t.removeAttribute("_src"))}})},window.addEventListener("scroll",img_lazyload),window.addEventListener("resize",img_lazyload),img_lazyload();
</script>
</body>
</html>
<?php
require_once '../include/gzip.php';
require_once $root_prefix.'include/functions.php';
$appkey = 'da44a5d9227fa9ef';
$appsecret = 'ad875eed760f65ac5ade5f363ab05e42';
define('APPVER', '2.0.0');
define('APPBUILD', '375');
header('NO-CONVERT-IMG: 1');
header('Content-Type: text/html');
if (isset($_GET['set_buy_platform'])) {
if (in_array($_GET['set_buy_platform'], ['ios', 'android'])) {
setcookie('manga-buy-platform', $_GET['set_buy_platform'], 0x7fffffff, '/manga/', $domain, true, true);
}
$uri = '/manga/';
unset($_GET['set_buy_platform']);
if (!empty($_GET)) {
$uri .= '?'.http_build_query($_GET);
}
header('Location: '.$uri, true, 302);
exit;
}
$platform = 'ios';
if (!empty($_COOKIE['manga-buy-platform']) && $_COOKIE['manga-buy-platform'] == 'android') $platform = 'android';
define('PLATFORM', $platform);
function errordie($reason, $extraHead = '') {
?>
<!DOCTYPE html><html>
<head>
<meta charset="UTF-8" name="viewport" content="width=device-width,user-scalable=no">
<meta name="format-detection" content="telephone=no" />
<?php echo $extraHead?>
<title>bilibili漫画 阅读器 - BiliPlus</title>
<style>
body {width:95%;max-width:600px;margin:5px auto !important;background:#EFEFF4;cursor:default}
</style>
</head>
<body>
<p><?php echo $reason;?></p>
</body>
</html><?php
exit;
}
if ($_COOKIE['login'] != 2) {
errordie('未登录<br><a href="javascript:localStorage.enablePlayback=\'on\',location.href=\'/login\'">登录</a>');
}
if (isset($_GET['act'])) {
switch ($_GET['act']) {
case 'detail': {
require_once 'detail.php';
exit;
}
case 'batch_index': {
require_once 'batch_index.php';
exit;
}
case 'detail_preview': {
require_once 'detail_preview.php';
exit;
}
case 'read': {
require_once 'read.php';
exit;
}
case 'diff': {
require_once 'diff.php';
exit;
}
}
}
require_once 'listfav.php';
<?php
$page = 1;
if (!empty($_GET['page']) && preg_match('/^\d+$/', $_GET['page'])) {
$page = $_GET['page'];
}
if (isset($_GET['order'])) {
if (in_array($_GET['order'], ['add','last_update','last_read'])) {
setcookie('manga-order', $_GET['order'], 0x7fffffff, '/manga/', $domain, true, true);
}
$uri = '/manga/';
unset($_GET['order']);
if (!empty($_GET)) {
$uri .= '?'.http_build_query($_GET);
}
header('Location: '.$uri, true, 302);
exit;
}
$pagesize = 36;
$order = 2;
if (!empty($_COOKIE['manga-order'])) {
switch ($_COOKIE['manga-order']) {
case 'add': {
$order = 1;
break;
}
case 'last_read': {
$order = 3;
}
}
}
$favList = cget('http://manga.bilibili.com/twirp/bookshelf.v1.Bookshelf/ListFavorite', [
'post' => buildParam([
'access_key'=>$_COOKIE['access_key'],
'actionKey'=>'appkey',
'appkey'=>$appkey,
'build'=>APPBUILD,
'device'=>'phone',
'mobi_app'=>'iphone_comic',
'order'=>$order,
'page_num'=>$page,
'page_size'=>$pagesize,
'platform'=>'ios',
'ts'=>time(),
'version'=>APPVER,
], $appsecret)
]);
if (isset($_GET['api'])) {
header('Content-Type: application/json; charset="UTF-8"');
echo $favList; exit;
}
$favList = json_decode($favList, true);
if ($order == 2) usort($favList['data'], function ($a, $b) {return $a['last_ep_publish_time']>$b['last_ep_publish_time']?-1:1;});
$hasPrevPage = $page > 1;
$hasNextPage = count($favList['data']) >= $pagesize;
?>
<!DOCTYPE html><html>
<head>
<meta charset="UTF-8" name="viewport" content="width=device-width,user-scalable=no">
<meta name="format-detection" content="telephone=no" />
<meta name="referrer" content="never">
<title>书架 - bilibili漫画 阅读器 - BiliPlus</title>
<script src="materialize.min.js" defer></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
var elems = document.querySelectorAll('select');
var instances = M.FormSelect.init(elems, {});
});
</script>
<link rel="stylesheet" href="materialize.min.css" />
<style>
body {width:95%;max-width:600px;margin:5px auto;background:#EFEFF4;cursor:default}
.comic-cover{height:200px}
.disabled{pointer-events:none}
</style>
</head>
<body>
<div class="col s12">
<h2>书架</h2>
<p class="row">
<span class="col s6">&copy; Copyright <a href="https://manga.bilibili.com/m/" target="_blank">bilibili</a></span>
<span class="input-field col s4">
<select autocomplete="off" onchange="location.href=location.href+(location.href.indexOf('?')==-1?'?':'&')+'order='+this.value">
<option <?php if ($order == 1) echo 'selected ' ?>value="add">追漫</option>
<option <?php if ($order == 2) echo 'selected ' ?>value="last_update">更新</option>
<option <?php if ($order == 3) echo 'selected ' ?>value="last_read">阅读</option>
</select>
<label>排序:</label>
</span>
</p>
<div>
</div>
<?php
if ($hasPrevPage || $hasNextPage) {
?>
<ul class="pagination row s12">
<li class="col s2 waves-effect<?php if (!$hasPrevPage) echo ' disabled'; ?>"><a href="?page=<?php echo $page-1;?>"><</a></li>
<li class="col s8"></li>
<li class="col s2 waves-effect<?php if (!$hasNextPage) echo ' disabled'; ?>"><a href="?page=<?php echo $page+1;?>">></a></li>
</ul>
<?php
}
foreach ($favList['data'] as $item) {
?>
<div class="card horizontal">
<div class="card-image">
<img class="comic-cover" alt="封面" src="<?php echo str_replace('http:','',$item['vcover']);?>@400h.jpg">
</div>
<div class="card-stacked">
<div class="card-content">
<p><?php echo $item['title'];?></p>
<p>最近更新:[<?php echo $item['latest_ep_short_title'];?>] <?php echo $item['last_ep_publish_time'];?></p>
<p>上次阅读:<?php echo $item['last_ep_short_title'];?></p>
</div>
<div class="card-action">
<a href="?act=detail&mangaid=<?php echo $item['comic_id'];?>">查看目录</a>
</div>
</div>
</div>
<?php
}
if (empty($favList['data'])) {
?>
<p style="text-align:center">书架里没有漫画了</p>
<?php
}
if (count ($favList['data']) > 3 && ($hasPrevPage || $hasNextPage)) {
?>
<ul class="pagination row s12">
<li class="col s2 waves-effect<?php if (!$hasPrevPage) echo ' disabled'; ?>"><a href="?page=<?php echo $page-1;?>"><</a></li>
<li class="col s8"></li>
<li class="col s2 waves-effect<?php if (!$hasNextPage) echo ' disabled'; ?>"><a href="?page=<?php echo $page+1;?>">></a></li>
</ul>
<?php
}
?>
</div>
</body>
</html>
<?php
if (empty($_GET['mangaid']) || !preg_match('(^\d+$)', $_GET['mangaid']) ||
empty($_GET['epid']) || !preg_match('(^\d+$)', $_GET['epid'])) {
errordie('无效id');
}
function prettySize(int $s) {
$units = ['B','KB','MB','GB'];
$unit = 0;
if ($s < 1001) return $s.'B';
while ($s > 1000) {
$unit++;
$s/=1024;
}
return number_format($s, 2, '.', '') . $units[$unit];
}
chdir(__DIR__);
$idxDb = new PDO('sqlite:index.db');
$reseturl = false;
$mangaInfoStmt = $idxDb->prepare('SELECT * FROM manga_info WHERE id=?');
$mangaInfoStmt->execute([$_GET['mangaid']]);
$mangaInfo = $mangaInfoStmt->fetch(PDO::FETCH_ASSOC);
if (empty($mangaInfo)) {
header('Location: ?act=detail&mangaid='.$_GET['mangaid'], true, 302);
exit;
}
$mangaTitle = $mangaInfo['title'];
$epSize = 0;
$epTitle = 'ep_id '.$_GET['epid'];
$chapLink = [''];
$epList = json_decode(brotli_uncompress($mangaInfo['list']), true);
usort($epList, function ($a, $b) {return $a['ord'] > $b['ord'] ? 1 : -1;});
$epArr = array_map(function ($i) {return $i['id'];}, $epList);
$epIdx = array_search($_GET['epid'], $epArr);
if ($epIdx !== false) {
$epSize = $epList[$epIdx]['size'];
$epTitle = $epList[$epIdx]['short_title'];
if ($epIdx > 0) {
$chapLink[] = '<a href="?act=read&mangaid='.$mangaInfo['id'].'&epid='.$epList[$epIdx - 1]['id'].'">上一话:'.$epList[$epIdx - 1]['short_title'].'</a>('.prettySize($epList[$epIdx - 1]['size']).'';
}
if ($epIdx < count($epArr) - 1) {
$chapLink[] = '<a href="?act=read&mangaid='.$mangaInfo['id'].'&epid='.$epList[$epIdx + 1]['id'].'">下一话:'.$epList[$epIdx + 1]['short_title'].'</a>('.prettySize($epList[$epIdx + 1]['size']).'';
}
}
$epTitle = implode(' - ', [$epTitle, $mangaTitle]);
$stmt = $idxDb->prepare('SELECT data,hash FROM index_data WHERE epid=?');
$curl = curl_init();
$stmt->execute([$_GET['epid']]);
$row = $stmt->fetch();
if (!empty($row) && !isset($_GET['refetch_index'])) {
$indexData = json_decode(brotli_uncompress($row['data']), true);
$hash = $row['hash'];
} else {
if (isset($_GET['buy']) && isset($_GET['payid'])) {
$reseturl = true;
$payParam = [
'access_key'=>$_COOKIE['access_key'],
'actionKey'=>'appkey',
'appkey'=>$appkey,
'build'=>APPBUILD,
'buy_method'=>$_GET['buy'],
'device'=>'phone',
'ep_id'=>$_GET['epid'],
'mobi_app'=>'iphone_comic',
'platform'=>PLATFORM,
'ts'=>time(),
'version'=>APPVER,
];
if ($_GET['buy'] == '2') {
$payParam['auto_pay_coupons_status'] = '2';
$payParam['coupon_id'] = $_GET['payid'];
} else if ($_GET['buy'] == '3') {
$payParam['auto_pay_gold_status'] = '2';
$payParam['pay_amount'] = $_GET['payid'];
} else {
errordie('购买参数无效');
}
$payResult = json_decode(cget('http://manga.bilibili.com/twirp/comic.v1.Comic/BuyEpisode', [
'post' => buildParam($payParam, $appsecret),
'curl'=>$curl
]), true);
if ($payResult['code'] !== 0) {
errordie('购买出错: ['.$payResult['code'].']'.$payResult['msg']);
}
}
if (isset($_GET['refetch_index'])) $reseturl = true;
$idxUrl = json_decode(cget('http://manga.bilibili.com/twirp/comic.v1.Comic/GetImageIndex', [
'post' => buildParam([
'access_key'=>$_COOKIE['access_key'],
'actionKey'=>'appkey',
'appkey'=>$appkey,
'build'=>APPBUILD,
'device'=>'phone',
'ep_id'=>$_GET['epid'],
'mobi_app'=>'iphone_comic',
'platform'=>'ios',
'ts'=>time(),
'version'=>APPVER,
], $appsecret),
'curl'=>$curl
]), true);
if ($idxUrl['code'] == 1 && $idxUrl['msg'] == 'need buy episode') {
$buyInfo = json_decode(cget('http://manga.bilibili.com/twirp/comic.v1.Comic/GetEpisodeBuyInfo', [
'post' => buildParam([
'access_key'=>$_COOKIE['access_key'],
'actionKey'=>'appkey',
'appkey'=>$appkey,
'build'=>APPBUILD,
'device'=>'phone',
'ep_id'=>$_GET['epid'],
'mobi_app'=>'iphone_comic',
'platform'=>PLATFORM,
'ts'=>time(),
'version'=>APPVER,
], $appsecret),
'curl'=>$curl
]), true);
if ($buyInfo['code'] !== 0) {
errordie('获取购买信息出错: ['.$idxUrl['code'].']'.$idxUrl['msg']);
}
errordie(implode('',[
'<h5>未购买章节</h5><h5>'.str_replace(['<','>'],['&lt','&gt;'], $epTitle).'</h5>',
'<p>'.prettySize($epSize).'</p>',
'<p>钱包:'.$buyInfo['data']['remain_gold'].' 漫币 | '.$buyInfo['data']['remain_coupon'].' 漫读券</p>',
'<p>当前钱包平台:'.PLATFORM.' <a href="?'.$_SERVER['QUERY_STRING'].'&set_buy_platform='.['ios'=>'android','android'=>'ios'][PLATFORM].'" class="waves-effect waves-light btn">切换至'.['ios'=>'android','android'=>'ios'][PLATFORM].'</a></p>',
'<p><a href="/html/reply.htm#type=29&id='.$_GET['epid'].'&title='.rawurlencode($epTitle).'" target="_blank">评论区</a>'.implode(' | ', $chapLink).'</p>',
'<p class="row s12"><a class="col s1"></a>',
'<a href="?'.$_SERVER['QUERY_STRING'].'&buy=3&payid='.$buyInfo['data']['pay_gold'].'" class="col s4 waves-effect waves-light btn'.($buyInfo['data']['remain_gold']<$buyInfo['data']['pay_gold']?' disabled':'').'">'.$buyInfo['data']['pay_gold'].' 漫币购买</a>',
'<a class="col s2"></a>',
'<a href="?'.$_SERVER['QUERY_STRING'].'&buy=2&payid='.$buyInfo['data']['recommend_coupon_id'].'" class="col s4 waves-effect waves-light btn'.($buyInfo['data']['remain_coupon']<1?' disabled':'').'">1 漫读券购买</a>',
'<a class="col s1"></a></p>',
'<p style="text-align:center"><img src="'.$buyInfo['data']['first_image_url'].'@1000w.jpg?token='.$buyInfo['data']['first_image_token'].'" width="500"></p>'
]), '<meta name="referrer" content="never" /><script src="materialize.min.js"></script><link rel="stylesheet" href="materialize.min.css" />');
}
if ($idxUrl['code'] !== 0) {
errordie('获取索引出错: ['.$idxUrl['code'].']'.$idxUrl['msg']);
}
$idxCurl = curl_init();
$encryptedIndexData = cget('http://i0.hdslb.com' . $idxUrl['data']['path'], ['curl'=>$idxCurl, 'curlOPT'=>[CURLOPT_FILETIME=>true]]);
$remoteTime = curl_getinfo($idxCurl, CURLINFO_FILETIME);
preg_match('(/manga/(\d+)/(\d+)/data\.index)', $idxUrl['data']['path'], $idMatch);
if ($_GET['mangaid'] != $idMatch[1]) {
$reseturl = true;
$_GET['mangaid'] = $idMatch[1];
}
$indexDataStr = decryptBiliComicIndex($encryptedIndexData, $idMatch[1], $idMatch[2]);
if (!$indexDataStr) {
errordie('下载索引出错');
}
$indexData = @json_decode($indexDataStr, true);
if ($indexData == NULL) {
errordie('下载索引出错');
}
$store_stmt = $idxDb->prepare('REPLACE INTO index_data (epid,mangaid,hash,data) VALUES (?,?,?,CAST(? AS BLOB))');
$data = brotli_compress($indexDataStr, 9);
$hash = crc32($data);
$idxDb->beginTransaction();
$store_stmt->execute([$_GET['epid'], $_GET['mangaid'], $hash, $data]);
$idxDb->prepare('INSERT OR IGNORE INTO index_data_history SELECT *,? as time FROM index_data WHERE epid=?')->execute([time(), $_GET['epid']]);
$idxDb->commit();
}
$urls = [];
$sizeResized = [];
$setWidth = 700;
for ($i=0; $i<count($indexData['pics']); $i++) {
$append = '@'.($setWidth*2).'w.jpg';
if ($indexData['sizes'][$i]['cx'] <= $setWidth * 2) {
$append = '@.jpg';
}
$urls[] = 'https://i0.hdslb.com'.$indexData['pics'][$i].$append;
$sizeResized[] = [
$setWidth,
$setWidth / $indexData['sizes'][$i]['cx'] * $indexData['sizes'][$i]['cy']
];
}
$tokens = json_decode(cget('http://manga.bilibili.com/twirp/comic.v1.Comic/ImageToken', [
'post' => buildParam([
'access_key'=>$_COOKIE['access_key'],
'actionKey'=>'appkey',
'appkey'=>$appkey,
'build'=>APPBUILD,
'device'=>'phone',
'mobi_app'=>'iphone_comic',
'platform'=>'ios',
'ts'=>time(),
'urls'=>json_encode($urls),
'version'=>APPVER,
], $appsecret),
'curl' => $curl
]), true);
if (!$tokens || $tokens['code'] != 0) {
errordie('获取凭据出错: ['.$tokens['code'].']'.$tokens['msg']);
}
cget('http://manga.bilibili.com/twirp/bookshelf.v1.Bookshelf/AddHistory', [
'post' => buildParam([
'access_key'=>$_COOKIE['access_key'],
'actionKey'=>'appkey',
'appkey'=>$appkey,
'build'=>APPBUILD,
'comic_id'=>$_GET['mangaid'],
'device'=>'phone',
'ep_id'=>$_GET['epid'],
'mobi_app'=>'iphone_comic',
'platform'=>'ios',
'ts'=>time(),
'version'=>APPVER,
], $appsecret),
'curl' => $curl
]);
$historyCnt = $idxDb->prepare('SELECT count(epid) FROM index_data_history WHERE epid=?');
$historyCnt->execute([$_GET['epid']]);
$historyCnt = $historyCnt->fetch()[0];
if ($historyCnt>1) $chapLink[] = '<a target="_blank" href="?act=diff&epid='.$_GET['epid'].'">索引历史比对</a>('.$historyCnt.'';
$chapLink = implode(" | <!--\n |-->", $chapLink);
$idxTimeStmt = $idxDb->prepare('SELECT time FROM index_data_history WHERE epid=? AND hash=?');
$idxTimeStmt->execute([$_GET['epid'], $hash]);
$idxTime = $idxTimeStmt->fetch()[0];
$cleanGET = $_GET;
unset($cleanGET['buy']);
unset($cleanGET['payid']);
unset($cleanGET['refetch_index']);
?>
<!DOCTYPE html><html>
<head>
<meta charset="UTF-8" name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no" />
<meta name="referrer" content="never">
<title><?php echo str_replace(['<','>'],['&lt','&gt;'], $epTitle);?> - 阅读 - bilibili漫画 阅读器 - BiliPlus</title>
<script src="materialize.min.js"></script>
<link rel="stylesheet" href="materialize.min.css" />
<style>
body {width:100%;margin:5px 0;background:#EFEFF4;cursor:default;touch-action:manipulation;-webkit-text-size-adjust:none;}
.middle,#hoz-container:not(.hoz-container){width:95%;max-width:800px;margin:0 auto}
.comic-single{max-width:700px}
.hoz-container{direction: rtl;overflow-x:scroll;overflow-y:hidden;width:100%;min-width:750px;-webkit-overflow-scrolling:touch}
.hoz-container>div{text-align:center;width:<?php echo count($tokens['data']) * 704;?>px;direction:rtl;margin-right:calc(50% - 350px)}
.hoz-container .comic-single{vertical-align:top}
</style>
</head>
<body>
<div class="col s12">
<div class="middle">
<h5><?php echo str_replace(['<','>'],['&lt','&gt;'], $epTitle);?></h5>
<p>索引缓存于 <?php echo date('Y/m/d H:i:s', $idxTime)?></p>
<p>&copy; Copyright <a href="https://manga.bilibili.com/m/mc<?php echo $_GET['mangaid']?>/<?php echo $_GET['epid']?>" target="_blank">bilibili</a></p>
<p><!--
|--><a href="/html/reply.htm#type=29&id=<?php echo $_GET['epid']?>&title=<?php echo rawurlencode($epTitle)?>" target="_blank">评论区</a><span class="review"></span> | <!--
|--><a href="?<?php echo http_build_query($cleanGET);?>&refetch_index">刷新索引</a> | <!--
|--><a href="javascript:toggleScrolling();">切换滚动</a><?php echo $chapLink;?><!--
|--></p>
<p><label style="color:inherit"><input type="checkbox" id="hozScrollFix" style="opacity:initial;position:initial;pointer-events:initial">横向滚动图片渲染修复</label></p>
</div>
<div id="hoz-container"><div style="text-align:center;font-size:0"><!--
<?php
for ($i=0; $i < count($tokens['data']); $i++) {
?>
|--><img class="comic-single" title="<?php echo str_pad($i+1, 3, '0', STR_PAD_LEFT);?>" width="<?php echo $sizeResized[$i][0]?>" height="<?php echo $sizeResized[$i][1]?>" _src="<?php echo $tokens['data'][$i]['url'].'?token='.$tokens['data'][$i]['token'];?>"><!--
<?php
}
?>
|--></div></div>
<div class="middle"><p><!--
|--><a href="/html/reply.htm#type=29&id=<?php echo $_GET['epid']?>&title=<?php echo rawurlencode($epTitle)?>" target="_blank">评论区</a><span class="review"></span><?php echo $chapLink;?><!--
|--></p></div>
</div>
<div class="fixed-action-btn"><div style="background:rgba(255,255,255,.6);text-align:center;font-family:Arial"><span id="page">1</span><br>/<br><?php echo count($tokens['data']);?></div></div>
<script>
function review_count(data){if(data.code==0){[].slice.call(document.getElementsByClassName('review')).forEach(function(i){i.textContent=''+data.data.count+'';})}}
window.img_lazyload=function(){var t=[].slice.call(document.getElementsByTagName("img"));t.forEach(function(t){if(t.hasAttribute("_src")){var e=t.getBoundingClientRect();e.bottom<-100||e.top>innerHeight+1000||e.right<-1000||e.left>innerWidth+100||(t.setAttribute("src",t.getAttribute("_src")),t.removeAttribute("_src"))}})},window.addEventListener("scroll",img_lazyload),window.addEventListener("resize",img_lazyload),img_lazyload();
var singles = [].slice.call(document.getElementsByClassName("comic-single"))
singles.forEach(function (i) {i.addEventListener('click', imgViewSlide);i.addEventListener('load', hozFixLoad); i.addEventListener('error', loadError)});
function loadError() {
this.loadFailed = true;
}
function imgViewSlide(e) {
if (this.loadFailed) {
delete this.loadFailed;
this.src = this.src;
return;
}
var isBottomPartClick = e.clientY > innerHeight / 2, target = isBottomPartClick ? this.nextElementSibling : this.previousElementSibling;
if (target) {
if (hozScrolling) {
var containerBox = hozScrollEle.getBoundingClientRect(), targetBox = target.getBoundingClientRect();
hozScrollEle.scrollLeft += targetBox.left + targetBox.width / 2 - containerBox.left - containerBox.width / 2;
} else {
window.scrollTo(0, target.offsetTop);
}
}
}
window.addEventListener('scroll', function () {
if (hozScrolling) return;
var line = innerHeight / 2 + scrollY, page = 1;
for (var i=0; i<singles.length; i++) {
if (singles[i].offsetTop > line) break;
page = singles[i].title | 0;
}
document.getElementById('page').textContent = page;
});
var hozScrollEle = document.getElementById('hoz-container');
var hozScrolling = false;
var hozFixTimeout = null;
function hozFixLoad() {
if (!hozScrolling || hozFixTimeout) return;
hozFix();
}
function hozFix() {
if (!hozScrollFix.checked) return;
hozFixTimeout = 0;
hozScrolling = false;
hozScrollEle.classList.toggle('hoz-container');
hozScrollEle.offsetWidth;
hozScrollEle.classList.toggle('hoz-container');
setTimeout(function (){ hozScrolling = true; }, 100);
}
hozScrollEle.addEventListener('scroll', function () {
if (!hozScrolling) return;
clearTimeout(hozFixTimeout);
hozFixTimeout = setTimeout(hozFix, 150);
img_lazyload();
var line = innerWidth / 2, page = 1;
for (var i=0; i<singles.length; i++) {
var box = singles[i].getBoundingClientRect();
if (box.left + box.width < line) break;
page = singles[i].title | 0;
}
document.getElementById('page').textContent = page;
hozScrollPos = hozScrollEle.scrollLeft;
})
var hozScrollPos = 0;
window.addEventListener('resize', function () {
if (hozScrolling) hozScrollEle.scrollLeft = hozScrollPos;
})
function toggleScrolling() {
hozScrollEle.classList.toggle('hoz-container');
hozScrolling = hozScrollEle.classList.contains('hoz-container');
localStorage.hozScroll = hozScrolling ? 1 : 0;
hozScrollPos = hozScrollEle.scrollLeft;
}
if (localStorage.hozScroll == 1) toggleScrolling();
hozScrollFix.checked = localStorage.hozScrollFix == 1;
hozScrollFix.addEventListener('change', function () {
localStorage.hozScrollFix = this.checked ? 1 : 0;
})
<?php
if ($reseturl) {
?>
history.replaceState('', '', '?<?php echo http_build_query($cleanGET);?>')
<?php
}
?>
</script>
<script src="https://api.bilibili.com/x/reply/count?oid=<?php echo $_GET['epid'];?>&type=29&jsonp=jsonp&callback=review_count&_=<?php echo time();?>" async></script>
</body>
</html>
<?php
function decryptBiliComicIndex($data, $mangaid, $epid) {
if (substr($data, 0, 9) !== 'BILICOMIC') {
return false;
}
$body = substr($data, 9);
$size = strlen($body);
$out = str_repeat("\0", $size);
$key = [
$epid & 0xff,
$epid >> 8 & 0xff,
$epid >> 16 & 0xff,
$epid >> 24 & 0xff,
$mangaid & 0xff,
$mangaid >> 8 & 0xff,
$mangaid >> 16 & 0xff,
$mangaid >> 24 & 0xff
];
for ($i = 0; $i < $size; $i++) {
$byte = ord($body[$i]);
$byte ^= ($key[($i) % 8]);
$out[$i] = chr($byte);
}
return readBiliComicIndexZip(new MemoryStream($out));
}
function readBiliComicIndexZip(MemoryStream $out) {
$out->littleEndian = true;
$out->position = $out->size - 22;
$signature = $out->readData(4);
$num = $out->short;
$start = $out->short;
$numRecords = $out->short;
$total = $out->short;
$cdsize = $out->long;
$cdoffset = $out->long;
$commentlen = $out->short;
$out->position = $cdoffset + 4 + 2 * 6;
$crc = $out->ulong;
$compressedSize = $out->long;
$uncompressedSize = $out->long;
$out->position = 26;
$fnameLen = $out->short;
$extraLen = $out->short;
$out->position += $fnameLen + $extraLen;
$compressedData = $out->readData($compressedSize);
$data = gzinflate($compressedData);
if (crc32($data) !== $crc) {
return false;
}
return $data;
}
abstract class Stream {
abstract protected function read($length);
abstract public function seek($position);
abstract public function position();
abstract protected function getPos();
abstract protected function setPos($pos);
public function __get($name) {
switch($name) {
case 'position': return $this->getPos();
case 'bool': return $this->readBoolean();
case 'byte': return $this->readData(1);
case 'short': return $this->readInt16();
case 'ushort': return $this->readUint16();
case 'long': return $this->readInt32();
case 'ulong': return $this->readUint32();
case 'longlong': return $this->readInt64();
case 'ulonglong': return $this->readUint64();
case 'float': return $this->readFloat();
case 'double': return $this->readDouble();
case 'string': return $this->readStringToNull();
case 'line': return $this->readStringToReturn();
default: throw new Exception("Access undefined field ${name} of class ".get_class($this));
}
}
public function __set($name, $val) {
switch($name) {
case 'position': return $this->setPos($val);
default: throw new Exception("Assign value to undefined field ${name} of class ".get_class($this));
}
}
public $littleEndian = false;
public $size;
public function readStringToNull() {
$s = '';
while (ord($char = $this->read(1)) != 0) {
$s .= $char;
}
return $s;
}
public function readStringAt($pos) {
$current = $this->position;
$this->position = $pos;
$data = $this->string;
$this->position = $current;
return $data;
}
public function readStringToReturn() {
$s = '';
while ($this->position < $this->size && ($char = $this->read(1)) != "\n") {
$s .= $char;
}
return trim($s,"\r");
}
public function readBoolean() {
return ord($this->byte)>0;
}
public function readInt16() {
$uint = $this->readUint16();
$sint = unpack('s', pack('S', $uint))[1];
return $sint;
}
public function readUint16() {
$int = $this->read(2);
if (strlen($int) != 2) return 0;
return unpack($this->littleEndian?'v':'n', $int)[1];
}
public function readInt32() {
$uint = $this->readUint32();
$sint = unpack('l', pack('L', $uint))[1];
return $sint;
}
public function readUint32() {
$int = $this->read(4);
if (strlen($int) != 4) return 0;
return unpack($this->littleEndian?'V':'N', $int)[1];
}
public function readInt64() {
$uint = $this->readUint64();
$sint = unpack('q', pack('Q', $uint))[1];
return $sint;
}
public function readUint64() {
$int = $this->read(8);
if (strlen($int) != 8) return 0;
return unpack($this->littleEndian?'P':'J', $int)[1];
}
public function readFloat() {
$int = $this->read(4);
if (strlen($int) != 4) return 0;
if (!$this->littleEndian) $int = $int[3].$int[2].$int[1].$int[0];
return unpack(/*$this->littleEndian?'g':'G'*/ 'f', $int)[1];
}
public function readDouble() {
$int = $this->read(8);
if (strlen($int) != 8) return 0;
if (!$this->littleEndian) $int = $int[7].$int[6].$int[5].$int[4].$int[3].$int[2].$int[1].$int[0];
return unpack(/*$this->littleEndian?'e':'E'*/ 'd', $int)[1];
}
public function readData($size) {
if ($size <= 0) return '';
return $this->read($size);
}
public function readDataAt($pos, $size) {
$current = $this->position;
$this->position = $pos;
$data = $this->readData($size);
$this->position = $current;
return $data;
}
public function alignStream($alignment) {
$mod = $this->position % $alignment;
if ($mod != 0) {
$this->position += $alignment - $mod;
}
}
public function readAlignedString($len) {
$string = $this->readData($len);
$this->alignStream(4);
return $string;
}
}
class MemoryStream extends Stream {
private $data;
private $offset;
function __construct($data) {
$this->data = $data;
$this->size = strlen($data);
}
function __destruct() {
$this->data = NULL;
}
protected function read($length) {
$data = substr($this->data, $this->offset, $length);
$this->offset += $length;
return $data;
}
public function seek($position) {
$this->offset = $position;
}
public function write($newData) {
$this->data .= $newData;
$this->size += strlen($newData);
}
public function position() {
return $this->offset;
}
protected function getPos() {
return $this->offset;
}
protected function setPos($pos) {
$this->offset = $pos;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.