Completed
Push — master ( 8555c7...3d8b58 )
by Angus
02:42
created

Batoto::handleCustomFollow()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 29
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 22
nc 1
nop 3
dl 0
loc 29
rs 8.8571
c 0
b 0
f 0
ccs 0
cts 26
cp 0
crap 2
1
<?php declare(strict_types=1); defined('BASEPATH') OR exit('No direct script access allowed');
2
3
class Batoto extends Base_Site_Model {
4
	//Batoto is a bit tricky to track. Unlike MangaFox and MangaHere, it doesn't store anything in the title_url, which means we have to get the data via other methods.
5
	//One problem we have though, is the tracker must support multiple sites, so this means we need to do some weird things to track Batoto.
6
	//title_url is stored like: "ID:--:LANGUAGE"
7
	//chapter_urls are stored like "CHAPTER_ID:--:CHAPTER_NUMBER"
8
9
	public $titleFormat   = '/^[0-9]+:--:(?:English|Spanish|French|German|Portuguese|Turkish|Indonesian|Greek|Filipino|Italian|Polish|Thai|Malay|Hungarian|Romanian|Arabic|Hebrew|Russian|Vietnamese|Dutch)$/';
10
	//FIXME: We're not validating the chapter name since we don't know what all the possible valid characters can be
11
	//       Preferably we'd just use /^[0-9a-z]+:--:(v[0-9]+\/)?c[0-9]+(\.[0-9]+)?$/
12
	public $chapterFormat = '/^[0-9a-z]+:--:.+$/';
13
14
	public function getFullTitleURL(string $title_string) : string {
15
		//FIXME: This does not point to the language specific title page. Should ask if it is possible to set LANG as arg?
16
		//FIXME: This points to a generic URL which will redirect according to the ID. Preferably we'd try and get the exact URL from the title, but we can't pass it here.
17
		$title_parts = explode(':--:', $title_string);
18
		return "http://bato.to/comic/_/comics/-r".$title_parts[0];
19
	}
20
21 View Code Duplication
	public function getChapterData(string $title_string, string $chapter) : array {
22
		//$title_string isn't used here.
23
24
		$chapter_parts = explode(':--:', $chapter);
25
		return [
26
			'url'    => "http://bato.to/reader#" . $chapter_parts[0],
27
			'number' => $chapter_parts[1]
28
		];
29
	}
30
31
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
32
		$titleData = [];
33
34
		$title_parts = explode(':--:', $title_url);
35
		$fullURL     = $this->getFullTitleURL($title_url);
36
		$lang        = $title_parts[1]; //TODO: Validate title_lang from array?
37
38
39
		//Bato.to is annoying and locks stuff behind auth. See: https://github.com/DakuTree/manga-tracker/issues/14#issuecomment-233830855
40
		$cookies = [
41
			"lang_option={$lang}",
42
			"member_id={$this->config->item('batoto_cookie_member_id')}",
43
			"pass_hash={$this->config->item('batoto_cookie_pass_hash')}"
44
		];
45
		$content = $this->get_content($fullURL, implode("; ", $cookies), "", TRUE);
46
47
		$data = $this->parseTitleDataDOM(
48
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($full...', $cookies), '', TRUE) on line 45 can also be of type false; however, Base_Site_Model::parseTitleDataDOM() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
49
			$title_url,
50
			"//h1[@class='ipsType_pagetitle']",
51
			"//table[contains(@class, 'chapters_list')]/tbody/tr[2]",
52
			"td[last()]",
53
			"td/a[contains(@href,'reader')]",
54
			">Register now<"
55
		);
56
		if($data) {
57
			$titleData['title'] = html_entity_decode(trim($data['nodes_title']->textContent));
58
59
			///^(?:Vol\.(?<volume>\S+) )?(?:Ch.(?<chapter>[^\s:]+)(?:\s?-\s?(?<extra>[0-9]+))?):?.*/
60
			preg_match('/^(?:Vol\.(?<volume>\S+) )?(?:Ch.(?<chapter>[^\s:]+)(?:\s?-\s?(?<extra>[0-9]+))?):?.*/', trim($data['nodes_chapter']->nodeValue), $text);
61
			$titleData['latest_chapter'] = substr($data['nodes_chapter']->getAttribute('href'), 22) . ':--:' . ((!empty($text['volume']) ? 'v'.$text['volume'].'/' : '') . 'c'.$text['chapter'] . (!empty($text['extra']) ? '-'.$text['extra'] : ''));
62
63
			$dateString = $data['nodes_latest']->nodeValue;
64
			if($dateString == 'An hour ago') {
65
				$dateString = '1 hour ago';
66
			}
67
			$titleData['last_updated']   = date("Y-m-d H:i:s", strtotime(preg_replace('/ (-|\[A\]).*$/', '', $dateString)));
68
69
			if($firstGet && $lang == 'English') {
70
				//FIXME: English is forced due for now. See #78.
71
				$this->doCustomFollow($content['body'], ['id' => $title_parts[0], 'lang' => $lang]);
72
			}
73
		}
74
75
		return (!empty($titleData) ? $titleData : NULL);
76
	}
77
78
	public function cleanTitleDataDOM(string $data) : string {
79
		$data = preg_replace('/^[\s\S]+<!-- ::: CONTENT ::: -->/', '<!-- ::: CONTENT ::: -->', $data);
80
		$data = preg_replace('/<!-- end mainContent -->[\s\S]+$/', '<!-- end mainContent -->', $data);
81
		$data = preg_replace('/<div id=\'commentsStart\' class=\'ipsBox\'>[\s\S]+$/', '</div></div><!-- end mainContent -->', $data);
82
83
		return $data;
84
	}
85
86
	//FIXME: This entire thing feels like an awful implementation....BUT IT WORKS FOR NOW.
87
	public function handleCustomFollow(callable $callback, string $data = "", array $extra = []) {
88
		preg_match('/ipb\.vars\[\'secure_hash\'\]\s+=\s+\'(?<secure_hash>[0-9a-z]+)\';[\s\S]+ipb\.vars\[\'session_id\'\]\s+=\s+\'(?<session_id>[0-9a-z]+)\';/', $data, $text);
89
90
		$params = [
91
			's'          => $text['session_id'],
92
			'app'        => 'core',
93
			'module'     => 'ajax',
94
			'section'    => 'like',
95
			'do'         => 'save',
96
			'secure_key' => $text['secure_hash'],
97
			'f_app'      => 'ccs',
98
			'f_area'     => 'ccs_custom_database_3_records',
99
			'f_relid'    => $extra['id']
100
		];
101
		$formData = [
102
			'like_notify' => '0',
103
			'like_freq'   => 'immediate',
104
			'like_anon'   => '0'
105
		];
106
107
		$cookies = [
108
			"lang_option={$extra['lang']}",
109
			"member_id={$this->config->item('batoto_cookie_member_id')}",
110
			"pass_hash={$this->config->item('batoto_cookie_pass_hash')}"
111
		];
112
		$content = $this->get_content('http://bato.to/forums/index.php?'.http_build_query($params), implode("; ", $cookies), "", TRUE, TRUE, $formData);
113
114
		$callback($content, $extra['id']);
115
	}
116
	public function doCustomUpdate() {
117
		$titleDataList = [];
118
119
		$cookies = [
120
			"lang_option=English", //FIXME: English is forced due for now. See #78.
121
			"member_id={$this->config->item('batoto_cookie_member_id')}",
122
			"pass_hash={$this->config->item('batoto_cookie_pass_hash')}"
123
		];
124
		$content = $this->get_content("http://bato.to/myfollows", implode("; ", $cookies), "", TRUE);
125
		if(!is_array($content)) {
126
			log_message('error', "{$this->site} /myfollows | Failed to grab URL (See above curl error)");
127
		} else {
128
			$headers     = $content['headers'];
0 ignored issues
show
Unused Code introduced by
$headers is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
129
			$status_code = $content['status_code'];
130
			$data        = $content['body'];
131
132
			if(!($status_code >= 200 && $status_code < 300)) {
133
				log_message('error', "{$this->site} /myfollows | Bad Status Code ({$status_code})");
134
			} else if(empty($data)) {
135
				log_message('error', "{$this->site} /myfollows | Data is empty? (Status code: {$status_code})");
136
			} else {
137
				$data = preg_replace('/^[\s\S]+<!-- ::: CONTENT ::: -->/', '<!-- ::: CONTENT ::: -->', $data);
138
				$data = preg_replace('/<!-- end mainContent -->[\s\S]+$/', '<!-- end mainContent -->', $data);
139
140
				$dom = new DOMDocument();
141
				libxml_use_internal_errors(TRUE);
142
				$dom->loadHTML($data);
143
				libxml_use_internal_errors(FALSE);
144
145
				$xpath      = new DOMXPath($dom);
146
				$nodes_rows = $xpath->query("//table[contains(@class, 'chapters_list')]/tbody/tr[position()>1]");
147
				if($nodes_rows->length > 0) {
148
					foreach($nodes_rows as $row) {
149
						$titleData = [];
150
151
						$nodes_title   = $xpath->query("td[2]/a[1]", $row);
152
						$nodes_chapter = $xpath->query("td[2]/a[2]", $row);
153
						$nodes_lang    = $xpath->query("td[3]/div", $row);
154
						$nodes_latest  = $xpath->query("td[5]", $row);
155
156
						if($nodes_lang->length === 1 && $nodes_lang->item(0)->getAttribute('title') == 'English') {
157
							if($nodes_title->length === 1 && $nodes_chapter->length === 1 && $nodes_latest->length === 1) {
158
								$title = $nodes_title->item(0);
159
160
								preg_match('/(?<id>[0-9]+)$/', $title->getAttribute('href'), $title_url_arr);
161
								$title_url = "{$title_url_arr['id']}:--:English"; //FIXME: English is currently forced, see #78
162
163
								if(!array_key_exists($title_url, $titleDataList)) {
164
									$titleData['title'] = trim($title->textContent);
165
166
									$chapter = $nodes_chapter->item(0);
167
									preg_match('/^(?:Vol\.(?<volume>\S+) )?(?:Ch.(?<chapter>[^\s:]+)(?:\s?-\s?(?<extra>[0-9]+))?):?.*/', trim($chapter->nodeValue), $text);
168
									$titleData['latest_chapter'] = substr($chapter->getAttribute('href'), 8) . ':--:' . ((!empty($text['volume']) ? 'v' . $text['volume'] . '/' : '') . 'c' . $text['chapter'] . (!empty($text['extra']) ? '-' . $text['extra'] : ''));
169
170
									$dateString = $nodes_latest->item(0)->nodeValue;
171
									if($dateString == 'An hour ago') {
172
										$dateString = '1 hour ago';
173
									}
174
									$titleData['last_updated'] = date("Y-m-d H:i:s", strtotime(preg_replace('/ (-|\[A\]).*$/', '', $dateString)));
175
176
177
									$titleDataList[$title_url] = $titleData;
178
								}
179
							} else {
180
								log_message('error', "{$this->site}/Custom | Invalid amount of nodes (TITLE: {$nodes_title->length} | CHAPTER: {$nodes_chapter->length}) | LATEST: {$nodes_latest->length})");
181
							}
182
						}
183
					}
184
				} else {
185
					log_message('error', '{$this->site} | Following list is empty?');
186
				}
187
			}
188
		}
189
		return $titleDataList;
190
	}
191
	public function doCustomCheck(string $oldChapterString, string $newChapterString) {
192
		$status = FALSE;
193
194
		$oldChapterSegments = explode('/', $this->getChapterData('', $oldChapterString)['number']);
195
		$newChapterSegments = explode('/', $this->getChapterData('', $newChapterString)['number']);
196
197
		//Although it's rare, it's possible for new chapters to have a different amount of segments to the oldChapter (or vice versa).
198
		//Since this can cause errors, we just throw a fail.
199
		$count = count($newChapterSegments);
200 View Code Duplication
		if($count === count($oldChapterSegments)) {
201
			if($count === 2) {
202
				//FIXME: This feels like a mess.
203
				$oldVolume = substr(array_shift($oldChapterSegments), 1);
204
				$newVolume = substr(array_shift($newChapterSegments), 1);
205
206
				if(in_array($oldVolume, ['TBD', 'TBA', 'NA', 'LMT'])) $oldVolume = 999;
207
				if(in_array($newVolume, ['TBD', 'TBA', 'NA', 'LMT'])) $newVolume = 999;
208
209
				$oldVolume = floatval($oldVolume);
210
				$newVolume = floatval($newVolume);
211
			} else {
212
				$oldVolume = 0;
213
				$newVolume = 0;
214
			}
215
			$oldChapter = floatval(substr(array_shift($oldChapterSegments), 1));
216
			$newChapter = floatval(substr(array_shift($newChapterSegments), 1));
217
218
			if($newVolume > $oldVolume) {
219
				//$newVolume is higher, no need to check chapter.
220
				$status = TRUE;
221
			} elseif($newChapter > $oldChapter) {
222
				//$newVolume isn't higher, but chapter is.
223
				$status = TRUE;
224
			}
225
		}
226
227
		return $status;
228
	}
229
}
230