mangadex/api/index.php
2021-03-14 14:31:55 -07:00

465 lines
16 KiB
PHP

<?php
/*
if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'Tachi') !== FALSE && rand(1,10) <3) {
http_response_code(600);
die('API currently down');
}*/
if (isset($_GET['_'])) {
http_response_code(666);
die();
}
use Mangadex\Model\Guard;
require_once ('../bootstrap.php');
require_once (ABSPATH . "/scripts/header.req.php");
if (!process_user_limit(1500, 'api_')) {
$array['status'] = 'error';
$array['message'] = 'Too many hits detected from your IP! Please try again tomorrow.';
header('Content-Type: application/json');
http_response_code(429);
die(json_encode($array));
}
$guard = Guard::getInstance();
if (isset($_COOKIE[SESSION_COOKIE_NAME]) || isset($_COOKIE[SESSION_REMEMBERME_COOKIE_NAME])) {
$guard->tryRestoreSession($_COOKIE[SESSION_COOKIE_NAME] ?? null, $_COOKIE[SESSION_REMEMBERME_COOKIE_NAME] ?? null);
$user = $guard->hasUser() ? $guard->getUser() : $guard->getUser(0); // Fetch guest record (userid=0) if no user could be restored
} else {
$user = $guard->getUser(0); // Fetch guest
}
/** @var $sentry Raven_Client */
if (isset($sentry) && isset($user)) {
$sentry->user_context([
'id' => $user->user_id,
'username' => $user->username,
]);
}
$type = $_GET['type'] ?? '';
switch ($type) {
case 'manga':
if (!isset($_GET['id'])) {
$array['status'] = 'error';
$array['message'] = 'No ID provided.';
http_response_code(400);
header('Content-Type: application/json');
die(json_encode($array));
}
$manga_id = (int)prepare_numeric($_GET['id']);
$manga = new Manga($manga_id);
if (isset($manga->manga_id)) {
$array['manga'] = [
'cover_url' => "/images/manga/$manga->manga_id.$manga->manga_image?" . @filemtime(ABS_DATA_BASEPATH . "/manga/$manga->manga_id.$manga->manga_image"),
'description' => $manga->manga_description,
'title' => $manga->manga_name,
'alt_names' => \array_map(function ($alt_name) { return \html_entity_decode($alt_name); }, $manga->get_manga_alt_names()),
'artist' => $manga->manga_artist,
'author' => $manga->manga_author,
'status' => $manga->manga_status_id,
'demographic' => $manga->manga_demo_id,
'genres' => $manga->get_manga_genres(),
'last_chapter' => $manga->manga_last_chapter,
'last_volume' => $manga->manga_last_volume,
'last_updated' => date('Y-m-d H:i:s', $manga->manga_last_uploaded),
'lang_name' => $manga->lang_name,
'lang_flag' => $manga->lang_flag,
'hentai' => $manga->manga_hentai,
//'follow' => $manga->get_user_follow_info($user->user_id),
'links' => json_decode($manga->manga_links),
'related' => $manga->get_related_manga(),
'rating' => [
'bayesian' => $manga->manga_bayesian ?? 0,
'mean' => $manga->manga_rating ?? 0,
'users' => number_format(count($manga->get_user_ratings() ?? 0)),
//'personal' => $manga->get_user_rating($user->user_id) ?: 0,
],
'views' => $manga->manga_views,
'follows' => $manga->manga_follows,
'comments' => $manga->thread_posts,
'last_updated' => $manga->manga_last_uploaded,
'covers' => \array_map(function ($cover) use ($manga_id) { return "/images/covers/{$manga_id}v{$cover['volume']}.{$cover['img']}"; }, $manga->get_covers()),
];
$search["chapter_deleted"] = 0;
$search["manga_id"] = $manga_id; //manga_id
$search["available"] = 1; //available
$blocked_groups = $user->get_blocked_groups();
if ($blocked_groups)
$search['blocked_groups'] = array_keys($blocked_groups);
$order = "(CASE volume WHEN '' THEN 1 END) DESC, abs(volume) DESC, abs(chapter) DESC, group_id ASC";
$chapters = new Chapters($search);
$chapters_obj = $chapters->query_read($order, 8000, 1);
$group_list = array();
foreach ($chapters_obj as $chapter) {
$chapter = (object)$chapter;
$array['chapter'][$chapter->chapter_id] = [
'volume' => $chapter->volume,
'chapter' => $chapter->chapter,
'title' => html_entity_decode($chapter->title),
'lang_name' => $chapter->lang_name,
'lang_code' => $chapter->lang_flag,
'group_id' => $chapter->group_id,
'group_name' => $chapter->group_name,
'group_id_2' => $chapter->group_id_2,
'group_name_2' => $chapter->group_name_2,
'group_id_3' => $chapter->group_id_3,
'group_name_3' => $chapter->group_name_3,
'timestamp' => $chapter->upload_timestamp,
'comments' => $chapter->thread_posts,
];
$group_list[$chapter->group_id] = $chapter->group_name;
if($chapter->group_id_2){
$group_list[$chapter->group_id_2] = $chapter->group_name_2;
}
if($chapter->group_id_3){
$group_list[$chapter->group_id_3] = $chapter->group_name_3;
}
}
foreach($group_list as $group_id => $group_name){
$array['group'][$group_id] = ['group_name' =>$group_name];
}
$array['status'] = 'OK';
/*
if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'Tachiyomi') !== false) {
$url = "https://www.google-analytics.com/collect?";
$query = "/title/$manga->manga_id/" . slugify($manga->manga_name);
$data = array(
'v' => 1,
'tid' => 'UA-112305080-1',
//'tid' => 'UA-114714674-1',
'cid' => md5($ip),
't' => 'pageview',
'dp' => urlencode($query),
'dt' => $manga->manga_name . ' (Title) - Tachi API',
//'an' => 'Tachiyomi',
//'cd' => 'API',
'z' => rand(),
);
httpGet($url, http_build_query_read($data));
}*/
}
else {
$array['status'] = 'Manga ID does not exist.';
http_response_code(404);
}
break;
case 'covers':
if (!isset($_GET['id'])) {
$array['status'] = 'error';
$array['message'] = 'No ID provided.';
http_response_code(400);
header('Content-Type: application/json');
die(json_encode($array));
}
$manga_id = (int)prepare_numeric($_GET['id']);
$manga = new Manga($manga_id);
if (isset($manga->manga_id)) {
$array['covers'] = \array_map(function ($cover) use ($manga_id) { return "/images/covers/{$manga_id}v{$cover['volume']}.{$cover['img']}"; }, $manga->get_covers());
$array['status'] = 'OK';
}
else {
$array['status'] = 'Manga ID does not exist.';
http_response_code(404);
}
break;
case 'chapter':
if (isset($_GET['hash']) && preg_match('/^[a-z0-9]+$/i', $_GET['hash'])) {
$chapter_id = $sql->prep("chapter_{$_GET['hash']}",
" SELECT chapter_id FROM mangadex_chapters WHERE chapter_hash = ? LIMIT 1 ",
[$_GET['hash']], 'fetchColumn', '', 86400);
if (!$chapter_id) {
$array['hash'] = $_GET['hash'];
$array['status'] = 'error';
$array['message'] = 'Chapter does not exist';
http_response_code(404);
header('Content-Type: application/json');
die(json_encode($array));
}
} else if (!isset($_GET['id'])) {
$array['status'] = 'error';
$array['message'] = 'No ID provided.';
http_response_code(400);
header('Content-Type: application/json');
die(json_encode($array));
}
$chapter_id = $chapter_id ?? prepare_numeric($_GET['id']);
$chapter = new Chapter($chapter_id);
$chapter = (object)$chapter;
if (isset($chapter->chapter_id)) {
$target_group = new Group($chapter->group_id);
if ($target_group && isset($target_group->group_id) && $target_group->group_id > 0) {
$group_members_array = $target_group->get_members();
} else {
$group_members_array = [];
}
if ($chapter->group_id_2) {
$target_group2 = new Group($chapter->group_id_2);
if ($target_group2 && isset($target_group2->group_id) && $target_group2->group_id > 0) {
$group_members_array = array_merge($group_members_array, $target_group2->get_members());
}
}
if ($chapter->group_id_3) {
$target_group3 = new Group($chapter->group_id_3);
if ($target_group3 && isset($target_group3->group_id) && $target_group3->group_id > 0) {
$group_members_array = array_merge($group_members_array, $target_group3->get_members());
}
}
if (!$chapter->available && !validate_level($user, 'pr')) {
$array = [
'id' => $chapter->chapter_id,
'timestamp' => $chapter->upload_timestamp,
'volume' => $chapter->volume,
'chapter' => $chapter->chapter,
'title' => html_entity_decode($chapter->title),
'lang_name' => $chapter->lang_name,
'lang_code' => $chapter->lang_flag,
'manga_id' => $chapter->manga_id,
'comments' => $chapter->thread_posts,
'status' => 'unavailable',
];
http_response_code(300);
}
elseif ($chapter->chapter_deleted && !validate_level($user, 'pr')) {
$array = [
'id' => $chapter->chapter_id,
'status' => 'deleted',
];
http_response_code(410);
}
elseif ($chapter->upload_timestamp < $timestamp ||
($user->user_id == $chapter->user_id ||
validate_level($user, 'pr') || // Retain pr ability to read delayed chapters
($user->user_id && in_array($user->user_id, [$chapter->group_leader_id, $chapter->group_leader_id_2, $chapter->group_leader_id_3])) ||
in_array($user->username, $group_members_array)
)) {
$status = 'OK';
$manga = new Manga($chapter->manga_id);
$long_strip = in_array(36, $manga->get_manga_genres());
if (substr($chapter->page_order, 0, 4) === 'http') {
$page_array = [];
$status = 'external';
} else {
$arr = explode(",", $chapter->page_order);
$page_array = array_combine(range(1, count($arr)), array_values($arr));
}
$server_fallback = LOCAL_SERVER_URL;
$server_network = null;
// when a chapter does not exist on the local webserver, it gets an id. since all imageservers share the same data, we can assign any imageserver
// with the best location to the user.
if ($chapter->server > 0) {
if (isset($user->md_at_home) && $user->md_at_home && stripos($chapter->page_order, 'http') === false) {
try {
$subsubdomain = $mdAtHomeClient->getServerUrl($chapter->chapter_hash, explode(',', $chapter->page_order), _IP);
if (!empty($subsubdomain)) {
$server_network = $subsubdomain;
}
} catch (Throwable $t) {
trigger_error($t->getMessage(), E_USER_WARNING);
}
}
$server_id = -1;
// If a usersetting overwrites it, take this
if (isset($_GET['server'])) {
// if the parameter was trash, this returns -1
$server_id = get_server_id_by_code($_GET['server']);
}
if ($server_id < 1) {
// Try to select a region based server if we havent set one already
$server_id = get_server_id_by_geography();
}
if ($server_id > 0) {
$server_fallback = "https://s$server_id.mangadex.org";
}
}
$server = $server_network ?: $server_fallback;
$data_dir = (isset($_GET['saver']) && $_GET['saver']) ? '/data-saver/' : '/data/';
$array = [
'id' => $chapter->chapter_id,
'timestamp' => $chapter->upload_timestamp,
'hash' => $chapter->chapter_hash,
'volume' => $chapter->volume,
'chapter' => $chapter->chapter,
'title' => html_entity_decode($chapter->title),
'lang_name' => $chapter->lang_name,
'lang_code' => $chapter->lang_flag,
'manga_id' => $chapter->manga_id,
'group_id' => $chapter->group_id,
'group_name' => $chapter->group_name,
'group_id_2' => $chapter->group_id_2,
'group_name_2' => $chapter->group_name_2,
'group_id_3' => $chapter->group_id_3,
'group_name_3' => $chapter->group_name_3,
'comments' => $chapter->thread_posts,
'server' => $server.$data_dir,
'page_array' => array_values($page_array),
'long_strip' => $long_strip,
'status' => $status,
];
if (!empty($server_network)) {
$array['server_fallback'] = $server_fallback.$data_dir;
}
if ($status === 'external') {
$array['external'] = $chapter->page_order;
}
elseif (in_array($chapter->manga_id, RESTRICTED_MANGA_IDS) && !validate_level($user, 'contributor') && $user->get_chapters_read_count() < MINIMUM_CHAPTERS_READ_FOR_RESTRICTED_MANGA) {
$array = [
'id' => $chapter->chapter_id,
'status' => 'restricted',
];
http_response_code(451);
}
update_views_v2($type, $chapter->chapter_id, $ip, $user->user_id);
$mark_read = $_GET["mark_read"] ?? true;
if ($user->user_id && $mark_read) {
$chapter->update_chapter_views($user->user_id, $manga->get_follows_user_id());
$chapter->update_reading_history($user->user_id, $user->get_reading_history(true));
$followed_manga_ids_array = $user->get_followed_manga_ids();
if (isset($followed_manga_ids_array[$chapter->manga_id])) {
if ((int) $followed_manga_ids_array[$chapter->manga_id]['chapter'] == (int) $chapter->chapter - 1)
$sql->modify('increment_chapter', ' UPDATE mangadex_follow_user_manga SET chapter = ABS(chapter) + 1 WHERE manga_id = ? AND user_id = ? LIMIT 1 ', [$chapter->manga_id, $user->user_id]);
if ((int) $followed_manga_ids_array[$chapter->manga_id]['volume'] == (int) $chapter->volume - 1)
$sql->modify('increment_volume', ' UPDATE mangadex_follow_user_manga SET volume = ABS(volume) + 1 WHERE manga_id = ? AND user_id = ? LIMIT 1 ', [$chapter->manga_id, $user->user_id]);
$memcached->delete("user_{$user->user_id}_followed_manga_ids");
}
}
$is_tachi = (strpos($_SERVER['HTTP_USER_AGENT'] ?? '', 'Tachiyomi') !== false) ? 1 : 0;
[$total_hits, $tachi_hits] = $memcached->get("chapter_hits") ?: [0, 0];
$memcached->set("chapter_hits", [$total_hits + 1, $tachi_hits + $is_tachi]);
}
else {
$array = [
'id' => $chapter->chapter_id,
'timestamp' => $chapter->upload_timestamp,
'volume' => $chapter->volume,
'chapter' => $chapter->chapter,
'title' => html_entity_decode($chapter->title),
'lang_name' => $chapter->lang_name,
'lang_code' => $chapter->lang_flag,
'manga_id' => $chapter->manga_id,
'group_id' => $chapter->group_id,
'group_name' => $chapter->group_name,
'group_id_2' => $chapter->group_id_2,
'group_name_2' => $chapter->group_name_2,
'group_id_3' => $chapter->group_id_3,
'group_name_3' => $chapter->group_name_3,
'group_website' => $chapter->group_website,
'status' => 'delayed',
];
http_response_code(409);
}
}
else {
$array = [
'id' => (int) $chapter_id,
'status' => 'error',
'message' => 'Chapter ID does not exist.',
];
http_response_code(404);
}
break;
case 'manga_follows':
if ($user && $user->user_id > 0) {
if (isset($_GET['manga_id']) && $_GET['manga_id'] > 0) {
$manga_id = (int)$_GET['manga_id'];
$query = <<<SQL
SELECT
m.manga_name AS title, f.manga_id, f.follow_type, f.volume, f.chapter
FROM
mangadex_follow_user_manga f,
mangadex_mangas m
WHERE
f.user_id = ? AND f.manga_id = ?
AND f.manga_id = m.manga_id
SQL;
$follows = $sql->prep('api_folows_one_of_any_user', $query, [$user->user_id, $manga_id], 'fetchAll', \PDO::FETCH_ASSOC, -1);
} else {
$limit = 200;
$offset = $limit * ((int) max(1, (int) min(50, $_GET['page'] ?? 1)) - 1);
$follows = $user->get_followed_manga_ids_api();
foreach ($follows AS &$follow) {
$follow['title'] = html_entity_decode($follow['title'] ?? '', null, 'UTF-8');
}
}
$array = [
'result' => $follows,
];
} else {
http_response_code(401);
$array['status'] = 'error';
$array['message'] = 'No User available. You need to authenticate to use this endpoint.';
}
break;
default:
$array['status'] = 'error';
$array['message'] = 'Not a valid API endpoint.';
http_response_code(404);
break;
}
//visit_log_api($_SERVER, $ip, $user->user_id, $user->hentai_mode);
header('Content-Type: application/json');
print json_encode($array);