Completed
Push — master ( 3560ba...257e65 )
by Angus
03:23
created

Batoto   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 199
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 199
ccs 0
cts 148
cp 0
rs 10
c 0
b 0
f 0
wmc 29
lcom 1
cbo 2

7 Methods

Rating   Name   Duplication   Size   Complexity  
A cleanTitleDataDOM() 0 7 1
B handleCustomFollow() 0 31 1
C doCustomUpdate() 0 75 16
A getFullTitleURL() 0 7 1
C getTitleData() 0 45 8
A getChapterData() 0 9 1
A doCustomCheck() 0 8 1
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
		//NOTE: This points to a generic URL which will redirect according to the ID.
17
		//      It's possible the title of a series can change, essentially making it possible for us to have multiple versions of the same title. This stops that.
18
		$title_parts = explode(':--:', $title_string);
19
		return "http://bato.to/comic/_/comics/-r".$title_parts[0];
20
	}
21
22
	public function getChapterData(string $title_string, string $chapter) : array {
23
		//$title_string isn't used here.
24
25
		$chapter_parts = explode(':--:', $chapter);
26
		return [
27
			'url'    => "http://bato.to/reader#" . $chapter_parts[0],
28
			'number' => $chapter_parts[1]
29
		];
30
	}
31
32
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
33
		$titleData = [];
34
35
		$title_parts = explode(':--:', $title_url);
36
		$fullURL     = $this->getFullTitleURL($title_url);
37
		$lang        = $title_parts[1]; //TODO: Validate title_lang from array?
38
39
40
		//Bato.to is annoying and locks stuff behind auth. See: https://github.com/DakuTree/manga-tracker/issues/14#issuecomment-233830855
41
		$cookies = [
42
			"lang_option={$lang}",
43
			"member_id={$this->config->item('batoto_cookie_member_id')}",
44
			"pass_hash={$this->config->item('batoto_cookie_pass_hash')}"
45
		];
46
		$content = $this->get_content($fullURL, implode("; ", $cookies), "", TRUE);
47
48
		$data = $this->parseTitleDataDOM(
49
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($full...', $cookies), '', TRUE) on line 46 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...
50
			$title_url,
51
			"//h1[@class='ipsType_pagetitle']",
52
			"//table[contains(@class, 'chapters_list')]/tbody/tr[2]",
53
			"td[last()]",
54
			"td/a[contains(@href,'reader')]",
55
			">Register now<"
56
		);
57
		if($data) {
58
			$titleData['title'] = html_entity_decode(trim($data['nodes_title']->textContent));
59
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
				$titleData = array_merge($titleData, $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'], function($body) {
115
			return strpos($body, '>Unfollow<') !== FALSE;
116
		});
117
	}
118
	public function doCustomUpdate() {
119
		$titleDataList = [];
120
121
		$cookies = [
122
			"lang_option=English", //FIXME: English is forced due for now. See #78.
123
			"member_id={$this->config->item('batoto_cookie_member_id')}",
124
			"pass_hash={$this->config->item('batoto_cookie_pass_hash')}"
125
		];
126
		$content = $this->get_content("http://bato.to/myfollows", implode("; ", $cookies), "", TRUE);
127
		if(!is_array($content)) {
128
			log_message('error', "{$this->site} /myfollows | Failed to grab URL (See above curl error)");
129
		} else {
130
			$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...
131
			$status_code = $content['status_code'];
132
			$data        = $content['body'];
133
134
			if(!($status_code >= 200 && $status_code < 300)) {
135
				log_message('error', "{$this->site} /myfollows | Bad Status Code ({$status_code})");
136
			} else if(empty($data)) {
137
				log_message('error', "{$this->site} /myfollows | Data is empty? (Status code: {$status_code})");
138
			} else {
139
				$data = preg_replace('/^[\s\S]+<!-- ::: CONTENT ::: -->/', '<!-- ::: CONTENT ::: -->', $data);
140
				$data = preg_replace('/<!-- end mainContent -->[\s\S]+$/', '<!-- end mainContent -->', $data);
141
142
				$dom = new DOMDocument();
143
				libxml_use_internal_errors(TRUE);
144
				$dom->loadHTML($data);
145
				libxml_use_internal_errors(FALSE);
146
147
				$xpath      = new DOMXPath($dom);
148
				$nodes_rows = $xpath->query("//table[contains(@class, 'chapters_list')]/tbody/tr[position()>1]");
149
				if($nodes_rows->length > 0) {
150
					foreach($nodes_rows as $row) {
151
						$titleData = [];
152
153
						$nodes_title   = $xpath->query("td[2]/a[1]", $row);
154
						$nodes_chapter = $xpath->query("td[2]/a[2]", $row);
155
						$nodes_lang    = $xpath->query("td[3]/div", $row);
156
						$nodes_latest  = $xpath->query("td[5]", $row);
157
158
						if($nodes_lang->length === 1 && $nodes_lang->item(0)->getAttribute('title') == 'English') {
159
							if($nodes_title->length === 1 && $nodes_chapter->length === 1 && $nodes_latest->length === 1) {
160
								$title = $nodes_title->item(0);
161
162
								preg_match('/(?<id>[0-9]+)$/', $title->getAttribute('href'), $title_url_arr);
163
								$title_url = "{$title_url_arr['id']}:--:English"; //FIXME: English is currently forced, see #78
164
165
								if(!array_key_exists($title_url, $titleDataList)) {
166
									$titleData['title'] = trim($title->textContent);
167
168
									$chapter = $nodes_chapter->item(0);
169
									preg_match('/^(?:Vol\.(?<volume>\S+) )?(?:Ch.(?<chapter>[^\s:]+)(?:\s?-\s?(?<extra>[0-9]+))?):?.*/', trim($chapter->nodeValue), $text);
170
									$titleData['latest_chapter'] = substr($chapter->getAttribute('href'), 8) . ':--:' . ((!empty($text['volume']) ? 'v' . $text['volume'] . '/' : '') . 'c' . $text['chapter'] . (!empty($text['extra']) ? '-' . $text['extra'] : ''));
171
172
									$dateString = $nodes_latest->item(0)->nodeValue;
173
									if($dateString == 'An hour ago') {
174
										$dateString = '1 hour ago';
175
									}
176
									$titleData['last_updated'] = date("Y-m-d H:i:s", strtotime(preg_replace('/ (-|\[A\]).*$/', '', $dateString)));
177
178
179
									$titleDataList[$title_url] = $titleData;
180
								}
181
							} else {
182
								log_message('error', "{$this->site}/Custom | Invalid amount of nodes (TITLE: {$nodes_title->length} | CHAPTER: {$nodes_chapter->length}) | LATEST: {$nodes_latest->length})");
183
							}
184
						}
185
					}
186
				} else {
187
					log_message('error', "{$this->site} | Following list is empty?");
188
				}
189
			}
190
		}
191
		return $titleDataList;
192
	}
193
	public function doCustomCheck(string $oldChapterString, string $newChapterString) {
194
		$oldChapterSegments = explode('/', $this->getChapterData('', $oldChapterString)['number']);
195
		$newChapterSegments = explode('/', $this->getChapterData('', $newChapterString)['number']);
196
197
		$status = $this->doCustomCheckCompare($oldChapterSegments, $newChapterSegments);
198
199
		return $status;
200
	}
201
}
202