Completed
Push — master ( bca649...551eca )
by Angus
02:40
created

Base_FoolSlide_Site_Model::doCustomUpdate()   C

Complexity

Conditions 11
Paths 3

Size

Total Lines 47
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 0
Metric Value
cc 11
eloc 29
nc 3
nop 0
dl 0
loc 47
ccs 0
cts 27
cp 0
crap 132
rs 5.2653
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1); defined('BASEPATH') OR exit('No direct script access allowed');
2
3
/**
4
 * Class Tracker_Sites_Model
5
 */
6
class Tracker_Sites_Model extends CI_Model {
7 119
	public function __construct() {
8 119
		parent::__construct();
9 119
	}
10
11
	public function __get($name) {
12
		//TODO: Is this a good idea? There wasn't a good consensus on if this is good practice or not..
13
		//      It's probably a minor speed reduction, but that isn't much of an issue.
14
		//      An alternate solution would simply have a function which generates a PHP file with code to load each model. Similar to: https://github.com/shish/shimmie2/blob/834bc740a4eeef751f546979e6400fd089db64f8/core/util.inc.php#L1422
15
		if(!class_exists($name) || !(in_array(get_parent_class($name), ['Base_Site_Model', 'Base_FoolSlide_Site_Model']))) {
16
			return get_instance()->{$name};
17
		} else {
18
			$this->loadSite($name);
19
			return $this->{$name};
20
		}
21
	}
22
23
	private function loadSite(string $siteName) : void {
24
		$this->{$siteName} = new $siteName();
25
	}
26
}
27
28
abstract class Base_Site_Model extends CI_Model {
29
	public $site          = '';
30
	public $titleFormat   = '//';
31
	public $chapterFormat = '//';
32
33
	/**
34
	 * 0: No custom updater.
35
	 * 1: Uses following page.
36
	 * 2: Uses latest releases page.
37
	 */
38
	public $customType = 0;
39
40 16
	public function __construct() {
41 16
		parent::__construct();
42
43 16
		$this->load->database();
44
45 16
		$this->site = get_class($this);
46 16
	}
47
48
	/**
49
	 * Generates URL to the title page of the requested series.
50
	 *
51
	 * NOTE: In some cases, we are required to store more data in the title_string than is needed to generate the URL. (Namely as the title_string is our unique identifier for that series)
52
	 *       When storing additional data, we use ':--:' as a delimiter to separate the data. Make sure to handle this as needed.
53
	 *
54
	 * Example:
55
	 *    return "http://mangafox.me/manga/{$title_url}/";
56
	 *
57
	 * Example (with extra data):
58
	 *    $title_parts = explode(':--:', title_url);
59
	 *    return "https://bato.to/comic/_/comics/-r".$title_parts[0];
60
	 *
61
	 * @param string $title_url
62
	 * @return string
63
	 */
64
	abstract public function getFullTitleURL(string $title_url) : string;
65
66
	/**
67
	 * Generates chapter data from given $title_url and $chapter.
68
	 *
69
	 * Chapter must be in a (v[0-9]+/)?c[0-9]+(\..+)? format.
70
	 *
71
	 * NOTE: In some cases, we are required to store the chapter number, and the segment required to generate the chapter URL separately.
72
	 *       Much like when generating the title URL, we use ':--:' as a delimiter to separate the data. Make sure to handle this as needed.
73
	 *
74
	 * Example:
75
	 *     return [
76
	 *        'url'    => $this->getFullTitleURL($title_url).'/'.$chapter,
77
	 *        'number' => "c{$chapter}"
78
	 *    ];
79
	 *
80
	 * @param string $title_url
81
	 * @param string $chapter
82
	 * @return array [url, number]
83
	 */
84
	abstract public function getChapterData(string $title_url, string $chapter) : array;
85
86
	/**
87
	 * Used to get the latest chapter of given $title_url.
88
	 *
89
	 * This <should> utilize both get_content and parseTitleDataDOM functions when possible, as these can both reduce a lot of the code required to set this up.
90
	 *
91
	 * $titleData params must be set accordingly:
92
	 * * `title` should always be used with html_entity_decode.
93
	 * * `latest_chapter` must match $this->chapterFormat.
94
	 * * `last_updated` should always be in date("Y-m-d H:i:s") format.
95
	 * * `followed` should never be set within via getTitleData, with the exception of via a array_merge with doCustomFollow.
96
	 *
97
	 * $firstGet is set to true when the series is first added to the DB, and is used to follow the series on given site (if possible).
98
	 *
99
	 * @param string $title_url
100
	 * @param bool   $firstGet
101
	 * @return array|null [title,latest_chapter,last_updated,followed?]
102
	 */
103
	abstract public function getTitleData(string $title_url, bool $firstGet = FALSE) : ?array;
104
105
	/**
106
	 * Validates given $title_url against titleFormat.
107
	 *
108
	 * Failure to match against titleFormat will stop the series from being added to the DB.
109
	 *
110
	 * @param string $title_url
111
	 * @return bool
112
	 */
113 2
	final public function isValidTitleURL(string $title_url) : bool {
114 2
		$success = (bool) preg_match($this->titleFormat, $title_url);
115 2
		if(!$success) log_message('error', "Invalid Title URL ({$this->site}): {$title_url}");
116 2
		return $success;
117
	}
118
119
	/**
120
	 * Validates given $chapter against chapterFormat.
121
	 *
122
	 * Failure to match against chapterFormat will stop the chapter being updated.
123
	 *
124
	 * @param string $chapter
125
	 * @return bool
126
	 */
127 2
	final public function isValidChapter(string $chapter) : bool {
128 2
		$success = (bool) preg_match($this->chapterFormat, $chapter);
129 2
		if(!$success) log_message('error', "Invalid Chapter ({$this->site}): {$chapter}");
130 2
		return $success;
131
	}
132
133
	/**
134
	 * Used by getTitleData (& similar functions) to get the requested page data.
135
	 *
136
	 * @param string $url
137
	 * @param string $cookie_string
138
	 * @param string $cookiejar_path
139
	 * @param bool   $follow_redirect
140
	 * @param bool   $isPost
141
	 * @param array  $postFields
142
	 *
143
	 * @return array|bool
144
	 */
145
	final protected function get_content(string $url, string $cookie_string = "", string $cookiejar_path = "", bool $follow_redirect = FALSE, bool $isPost = FALSE, array $postFields = []) {
146
		$ch = curl_init();
147
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
148
		curl_setopt($ch, CURLOPT_ENCODING , "gzip");
149
		//curl_setopt($ch, CURLOPT_VERBOSE, 1);
150
		curl_setopt($ch, CURLOPT_HEADER, 1);
151
152
		if($follow_redirect)        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
153
154
		if(!empty($cookie_string))  curl_setopt($ch, CURLOPT_COOKIE, $cookie_string);
155
		if(!empty($cookiejar_path)) curl_setopt($ch, CURLOPT_COOKIEFILE, $cookiejar_path);
156
157
		//Some sites check the useragent for stuff, use a pre-defined user-agent to avoid stuff.
158
		curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2824.0 Safari/537.36');
159
160
		//NOTE: This is required for SSL URLs for now. Without it we tend to get error code 60.
161
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); //FIXME: This isn't safe, but it allows us to grab SSL URLs
162
163
		curl_setopt($ch, CURLOPT_URL, $url);
164
165
		if($isPost) {
166
			curl_setopt($ch,CURLOPT_POST, count($postFields));
167
			curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query($postFields));
168
		}
169
170
		$response = curl_exec($ch);
171
		if($response === FALSE) {
172
			log_message('error', "curl failed with error: ".curl_errno($ch)." | ".curl_error($ch));
173
			//FIXME: We don't always account for FALSE return
174
			return FALSE;
175
		}
176
177
		$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
178
		$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
179
		$headers     = http_parse_headers(substr($response, 0, $header_size));
180
		$body        = substr($response, $header_size);
181
		curl_close($ch);
182
183
		return [
184
			'headers'     => $headers,
185
			'status_code' => $status_code,
186
			'body'        => $body
187
		];
188
	}
189
190
	/**
191
	 * Used by getTitleData to get the title, latest_chapter & last_updated data from the data returned by get_content.
192
	 *
193
	 * parseTitleDataDOM checks if the data returned by get_content is valid via a few simple checks.
194
	 * * If the request was actually successful, had a valid status code & data wasn't empty. We also do an additional check on an optional $failure_string param, which will throw a failure if it's matched.
195
	 *
196
	 * Data is cleaned by cleanTitleDataDOM prior to being passed to DOMDocument.
197
	 *
198
	 * All $node_* params must be XPath to the requested node, and must only return 1 result. Anything else will throw a failure.
199
	 *
200
	 * @param array  $content
201
	 * @param string $title_url
202
	 * @param string $node_title_string
203
	 * @param string $node_row_string
204
	 * @param string $node_latest_string
205
	 * @param string $node_chapter_string
206
	 * @param string $failure_string
207
	 * @return DOMElement[]|false [nodes_title,nodes_chapter,nodes_latest]
208
	 */
209
	final protected function parseTitleDataDOM(
210
		$content, string $title_url,
211
		string $node_title_string, string $node_row_string,
212
		string $node_latest_string, string $node_chapter_string,
213
		string $failure_string = "") {
214
215
		if(!is_array($content)) {
216
			log_message('error', "{$this->site} : {$title_url} | Failed to grab URL (See above curl error)");
217
		} else {
218
			list('headers' => $headers, 'status_code' => $status_code, 'body' => $data) = $content;
0 ignored issues
show
Unused Code introduced by
The assignment to $headers is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
219
220
			if(!($status_code >= 200 && $status_code < 300)) {
221
				log_message('error', "{$this->site} : {$title_url} | Bad Status Code ({$status_code})");
222
			} else if(empty($data)) {
223
				log_message('error', "{$this->site} : {$title_url} | Data is empty? (Status code: {$status_code})");
224
			} else if($failure_string !== "" && strpos($data, $failure_string) !== FALSE) {
225
				log_message('error', "{$this->site} : {$title_url} | Failure string matched");
226
			} else {
227
				$data = $this->cleanTitleDataDOM($data); //This allows us to clean the DOM prior to parsing. It's faster to grab the only part we need THEN parse it.
228
229
				$dom = new DOMDocument();
230
				libxml_use_internal_errors(TRUE);
231
				$dom->loadHTML('<?xml encoding="utf-8" ?>' . $data);
232
				libxml_use_internal_errors(FALSE);
233
234
				$xpath = new DOMXPath($dom);
235
				$nodes_title = $xpath->query($node_title_string);
236
				$nodes_row   = $xpath->query($node_row_string);
237
				if($nodes_title->length === 1 && $nodes_row->length === 1) {
238
					$firstRow      = $nodes_row->item(0);
239
					$nodes_latest  = $xpath->query($node_latest_string,  $firstRow);
240
241
					if($node_chapter_string !== '') {
242
						$nodes_chapter = $xpath->query($node_chapter_string, $firstRow);
243
					} else {
244
						$nodes_chapter = $nodes_row;
245
					}
246
247
					if($nodes_latest->length === 1 && $nodes_chapter->length === 1) {
248
						return [
249
							'nodes_title'   => $nodes_title->item(0),
250
							'nodes_latest'  => $nodes_latest->item(0),
251
							'nodes_chapter' => $nodes_chapter->item(0)
252
						];
253
					} else {
254
						log_message('error', "{$this->site} : {$title_url} | Invalid amount of nodes (LATEST: {$nodes_latest->length} | CHAPTER: {$nodes_chapter->length})");
255
					}
256
				} else {
257
					log_message('error', "{$this->site} : {$title_url} | Invalid amount of nodes (TITLE: {$nodes_title->length} | ROW: {$nodes_row->length})");
258
				}
259
			}
260
		}
261
262
		return FALSE;
263
	}
264
265
	/**
266
	 * Used by parseTitleDataDOM to clean the data prior to passing it to DOMDocument & DOMXPath.
267
	 * This is mostly done as an (assumed) speed improvement due to the reduced amount of DOM to parse, or simply just making it easier to parse with XPath.
268
	 *
269
	 * @param string $data
270
	 * @return string
271
	 */
272
	public function cleanTitleDataDOM(string $data) : string {
273
		return $data;
274
	}
275
276
	/**
277
	 * Used to follow a series on given site if supported.
278
	 *
279
	 * This is called by getTitleData if $firstGet is true (which occurs when the series is first being added to the DB).
280
	 *
281
	 * Most of the actual following is done by handleCustomFollow.
282
	 *
283
	 * @param string $data
284
	 * @param array  $extra
285
	 * @return array
286
	 */
287
	final public function doCustomFollow(string $data = "", array $extra = []) : array {
288
		$titleData = [];
289
		$this->handleCustomFollow(function($content, $id, closure $successCallback = NULL) use(&$titleData) {
290
			if(is_array($content)) {
291
				if(array_key_exists('status_code', $content)) {
292
					$statusCode = $content['status_code'];
293
					if($statusCode === 200) {
294
						$isCallable = is_callable($successCallback);
295
						if(($isCallable && $successCallback($content['body'])) || !$isCallable) {
296
							$titleData['followed'] = 'Y';
297
298
							log_message('info', "doCustomFollow succeeded for {$id}");
299
						} else {
300
							log_message('error', "doCustomFollow failed (Invalid response?) for {$id}");
301
						}
302
					} else {
303
						log_message('error', "doCustomFollow failed (Invalid status code ({$statusCode})) for {$id}");
304
					}
305
				} else {
306
					log_message('error', "doCustomFollow failed (Missing status code?) for {$id}");
307
				}
308
			} else {
309
				log_message('error', "doCustomFollow failed (Failed request) for {$id}");
310
			}
311
		}, $data, $extra);
312
		return $titleData;
313
	}
314
315
	/**
316
	 * Used by doCustomFollow to handle following series on sites.
317
	 *
318
	 * Uses get_content to get data.
319
	 *
320
	 * $callback must return ($content, $id, closure $successCallback = NULL).
321
	 * * $content is simply just the get_content data.
322
	 * * $id is the dbID. This should be passed by the $extra arr.
323
	 * * $successCallback is an optional success check to make sure the series was properly followed.
324
	 *
325
	 * @param callable $callback
326
	 * @param string   $data
327
	 * @param array    $extra
328
	 */
329
	public function handleCustomFollow(callable $callback, string $data = "", array $extra = []) {}
330
331
	/**
332
	 * Used to check the sites following page for new updates (if supported).
333
	 * This should work much like getTitleData, but instead checks the following page.
334
	 *
335
	 * This must return an array containing arrays of each of the chapters data.
336
	 */
337
	public function doCustomUpdate() {}
338
339
	/**
340
	 * Used by the custom updater to check if a chapter looks newer than the current one.
341
	 *
342
	 * This calls doCustomCheckCompare which handles the majority of the checking.
343
	 * NOTE: Depending on the site, you may need to call getChapterData to get the chapter number to be used with this.
344
	 *
345
	 * @param string $oldChapter
346
	 * @param string $newChapter
347
	 */
348
	public function doCustomCheck(string $oldChapter, string $newChapter) {}
349
350
	/**
351
	 * Used by doCustomCheck to check if a chapter looks newer than the current one.
352
	 * Chapter must be in a (v[0-9]+/)?c[0-9]+(\..+)? format.
353
	 *
354
	 * To avoid issues with the occasional off case, this will only ever return true if we are 100% sure that the new chapter is newer than the old one.
355
	 *
356
	 * @param array $oldChapterSegments
357
	 * @param array $newChapterSegments
358
	 * @return bool
359
	 */
360 12
	final public function doCustomCheckCompare(array $oldChapterSegments, array $newChapterSegments) : bool {
361
		//NOTE: We only need to check against the new chapter here, as that is what is used for confirming update.
362 12
		$status = FALSE;
363
364
		//Make sure we have a volume element
365 12
		if(count($oldChapterSegments) === 1) array_unshift($oldChapterSegments, 'v0');
366 12
		if(count($newChapterSegments) === 1) array_unshift($newChapterSegments, 'v0');
367
368 12
		$oldCount = count($oldChapterSegments);
369 12
		$newCount = count($newChapterSegments);
370 12
		if($newCount === $oldCount) {
371
			//Make sure chapter format looks correct.
372
			//NOTE: We only need to check newCount as we know oldCount is the same count.
373 12
			if($newCount === 2) {
374
				//FIXME: Can we loop this?
375 12
				$oldVolume = substr(array_shift($oldChapterSegments), 1);
376 12
				$newVolume = substr(array_shift($newChapterSegments), 1);
377
378
				//Forcing volume to 0 as TBD might not be the latest (although it can be, but that is covered by other checks)
379 12
				if(in_array($oldVolume, ['TBD', 'TBA', 'NA', 'LMT'])) $oldVolume = 0;
380 12
				if(in_array($newVolume, ['TBD', 'TBA', 'NA', 'LMT'])) $newVolume = 0;
381
382 12
				$oldVolume = floatval($oldVolume);
383 12
				$newVolume = floatval($newVolume);
384
			} else {
385
				$oldVolume = 0;
386
				$newVolume = 0;
387
			}
388 12
			$oldChapter = floatval(substr(array_shift($oldChapterSegments), 1));
389 12
			$newChapter = floatval(substr(array_shift($newChapterSegments), 1));
390
391 12
			if($newChapter > $oldChapter && ($oldChapter >= 10 && $newChapter >= 10)) {
392
				//$newChapter is higher than $oldChapter AND $oldChapter and $newChapter are both more than 10
393
				//This is intended to cover the /majority/ of valid updates, as we technically shouldn't have to check volumes.
394
395 4
				$status = TRUE;
396 8
			} elseif($newVolume > $oldVolume && ($oldChapter < 10 && $newChapter < 10)) {
397
				//This is pretty much just to match a one-off case where the site doesn't properly increment chapter numbers across volumes, and instead does something like: v1/c1..v1/c5, v2/c1..v1/c5 (and so on).
398 1
				$status = TRUE;
399 7
			} elseif($newVolume > $oldVolume && $newChapter >= $oldChapter) {
400
				//$newVolume is higher, and chapter is higher so no need to check chapter.
401 2
				$status = TRUE;
402 5
			} elseif($newChapter > $oldChapter) {
403
				//$newVolume isn't higher, but chapter is.
404
				$status = TRUE;
405
			}
406
		}
407
408 12
		return $status;
409
	}
410
}
411
412
abstract class Base_FoolSlide_Site_Model extends Base_Site_Model {
413
	public $titleFormat   = '/^[a-z0-9_-]+$/';
414
	public $chapterFormat = '/^en(?:-us)?\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
415
	public $customType    = 2;
416
417
	public $baseURL = '';
418
419
	public function getFullTitleURL(string $title_url) : string {
420
		return "{$this->baseURL}/series/{$title_url}";
421
	}
422
423
	public function getChapterData(string $title_url, string $chapter) : array {
424
		$chapter_parts = explode('/', $chapter); //returns #LANG#/#VOLUME#/#CHAPTER#/#CHAPTER_EXTRA#(/#PAGE#/)
425
		return [
426
			'url'    => $this->getChapterURL($title_url, $chapter),
427
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
428
		];
429
	}
430
	public function getChapterURL(string $title_url, string $chapter) : string {
431
		return "{$this->baseURL}/read/{$title_url}/{$chapter}/";
432
	}
433
434
	public function getTitleData(string $title_url, bool $firstGet = FALSE) : ?array {
435
		$titleData = [];
436
437
		$jsonURL = $this->getJSONTitleURL($title_url);
438
		if($content = $this->get_content($jsonURL)) {
439
			$json = json_decode($content['body'], TRUE);
440
			if($json && isset($json['chapters']) && count($json['chapters']) > 0) {
441
				$titleData['title'] = trim($json['comic']['name']);
442
443
				//FoolSlide title API doesn't appear to let you sort (yet every other API method which has chapters does, so we need to sort ourselves..
444
				usort($json['chapters'], function($a, $b) {
445
					return floatval("{$b['chapter']['chapter']}.{$b['chapter']['subchapter']}") <=> floatval("{$a['chapter']['chapter']}.{$a['chapter']['subchapter']}");
446
				});
447
				$latestChapter = reset($json['chapters'])['chapter'];
448
449
				$latestChapterString = "{$latestChapter['language']}/{$latestChapter['volume']}/{$latestChapter['chapter']}";
450
				if($latestChapter['subchapter'] !== '0') {
451
					$latestChapterString .= "/{$latestChapter['subchapter']}";
452
				}
453
				$titleData['latest_chapter'] = $latestChapterString;
454
455
				//No need to use date() here since this is already formatted as such.
456
				$titleData['last_updated'] = ($latestChapter['updated'] !== '0000-00-00 00:00:00' ? $latestChapter['updated'] : $latestChapter['created']);
457
			}
458
		}
459
460
		return (!empty($titleData) ? $titleData : NULL);
461
	}
462
463
	//Since we're just checking the latest updates page and not a following page, we just need to simulate a follow.
464
	//TODO: It would probably be better to have some kind of var which says that the custom update uses a following page..
465
	public function handleCustomFollow(callable $callback, string $data = "", array $extra = []) {
466
		$content = ['status_code' => 200];
467
		$callback($content, $extra['id']);
468
	}
469
	public function doCustomUpdate() {
470
		$titleDataList = [];
471
472
		$jsonURL = $this->getJSONUpdateURL();
473
		if(($content = $this->get_content($jsonURL)) && $content['status_code'] == 200) {
474
			if($json = json_decode($content['body'], TRUE) && isset($json['chapters'])) {
0 ignored issues
show
Bug introduced by
The variable $json seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $json = (json_decode($co...set($json['chapters'])), Probably Intended Meaning: ($json = json_decode($co...sset($json['chapters'])
Loading history...
475
				//This should fix edge cases where chapters are uploaded in bulk in the wrong order (HelveticaScans does this with Mousou Telepathy).
476
				usort($json['chapters'], function($a, $b) {
477
					$a_date = new DateTime($a['chapter']['updated'] !== '0000-00-00 00:00:00' ? $a['chapter']['updated'] : $a['chapter']['created']);
478
					$b_date = new DateTime($b['chapter']['updated'] !== '0000-00-00 00:00:00' ? $b['chapter']['updated'] : $b['chapter']['created']);
479
					return $b_date <=> $a_date;
480
				});
481
482
				$parsedTitles = [];
483
				foreach($json['chapters'] as $chapterData) {
484
					if(!in_array($chapterData['comic']['stub'], $parsedTitles)) {
485
						$parsedTitles[] = $chapterData['comic']['stub'];
486
487
						$titleData = [];
488
						$titleData['title'] = trim($chapterData['comic']['name']);
489
490
						$latestChapter = $chapterData['chapter'];
491
492
						$latestChapterString = "en/{$latestChapter['volume']}/{$latestChapter['chapter']}";
493
						if($latestChapter['subchapter'] !== '0') {
494
							$latestChapterString .= "/{$latestChapter['subchapter']}";
495
						}
496
						$titleData['latest_chapter'] = $latestChapterString;
497
498
						//No need to use date() here since this is already formatted as such.
499
						$titleData['last_updated'] = ($latestChapter['updated'] !== '0000-00-00 00:00:00' ? $latestChapter['updated'] : $latestChapter['created']);
500
501
						$titleDataList[$chapterData['comic']['stub']] = $titleData;
502
					} else {
503
						//We already have title data for this title.
504
						continue;
505
					}
506
				}
507
			} else {
508
				log_message('error', "{$this->site} - Custom updating failed (no chapters arg?) for {$this->baseURL}.");
509
			}
510
		} else {
511
			log_message('error', "{$this->site} - Custom updating failed for {$this->baseURL}.");
512
		}
513
514
		return $titleDataList;
515
	}
516
	public function doCustomCheck(string $oldChapterString, string $newChapterString) {
517
		$oldChapterSegments = explode('/', $this->getChapterData('', $oldChapterString)['number']);
518
		$newChapterSegments = explode('/', $this->getChapterData('', $newChapterString)['number']);
519
520
		$status = $this->doCustomCheckCompare($oldChapterSegments, $newChapterSegments);
521
522
		return $status;
523
	}
524
525
	public function getJSONTitleURL(string $title_url) : string {
526
		return "{$this->baseURL}/api/reader/comic/stub/{$title_url}/format/json";
527
	}
528
	public function getJSONUpdateURL() : string {
529
		return "{$this->baseURL}/api/reader/chapters/orderby/desc_created/format/json";
530
	}
531
}
532