Completed
Branch master (205409)
by Timothy
02:36
created

Kitsu::getServiceMetaData()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 35
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 22
nc 4
nop 1
dl 0
loc 35
rs 8.5806
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
/**
3
 * Anime List Client
4
 *
5
 * An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
6
 *
7
 * PHP version 7
8
 *
9
 * @package     AnimeListClient
10
 * @author      Timothy J. Warren <[email protected]>
11
 * @copyright   2015 - 2017  Timothy J. Warren
12
 * @license     http://www.opensource.org/licenses/mit-license.html  MIT License
13
 * @version     4.0
14
 * @link        https://github.com/timw4mail/HummingBirdAnimeClient
15
 */
16
17
namespace Aviat\AnimeClient\API;
18
19
use Aviat\AnimeClient\API\Kitsu\Enum\{
20
	AnimeAiringStatus,
21
	AnimeWatchingStatus,
22
	MangaReadingStatus
23
};
24
use DateTimeImmutable;
25
26
/**
27
 * Data massaging helpers for the Kitsu API
28
 */
29
class Kitsu {
30
	const AUTH_URL = 'https://kitsu.io/api/oauth/token';
31
	const AUTH_USER_ID_KEY = 'kitsu-auth-userid';
32
	const AUTH_TOKEN_CACHE_KEY = 'kitsu-auth-token';
33
34
	/**
35
	 * Map of Kitsu status to label for select menus
36
	 *
37
	 * @return array
38
	 */
39 View Code Duplication
	public static function getStatusToSelectMap()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
40
	{
41
		return [
42
			AnimeWatchingStatus::WATCHING => 'Currently Watching',
43
			AnimeWatchingStatus::PLAN_TO_WATCH => 'Plan to Watch',
44
			AnimeWatchingStatus::COMPLETED => 'Completed',
45
			AnimeWatchingStatus::ON_HOLD => 'On Hold',
46
			AnimeWatchingStatus::DROPPED => 'Dropped'
47
		];
48
	}
49
50
	/**
51
	 * Map of Kitsu Manga status to label for select menus
52
	 *
53
	 * @return array
54
	 */
55 View Code Duplication
	public static function getStatusToMangaSelectMap()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
56
	{
57
		return [
58
			MangaReadingStatus::READING => 'Currently Reading',
59
			MangaReadingStatus::PLAN_TO_READ => 'Plan to Read',
60
			MangaReadingStatus::COMPLETED => 'Completed',
61
			MangaReadingStatus::ON_HOLD => 'On Hold',
62
			MangaReadingStatus::DROPPED => 'Dropped'
63
		];
64
	}
65
66
	/**
67
	 * Determine whether an anime is airing, finished airing, or has not yet aired
68
	 *
69
	 * @param string $startDate
0 ignored issues
show
Documentation introduced by
Should the type for parameter $startDate not be null|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
70
	 * @param string $endDate
0 ignored issues
show
Documentation introduced by
Should the type for parameter $endDate not be null|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
71
	 * @return string
72
	 */
73
	public static function getAiringStatus(string $startDate = null, string $endDate = null): string
74
	{
75
		$startAirDate = new DateTimeImmutable($startDate ?? 'tomorrow');
76
		$endAirDate = new DateTimeImmutable($endDate ?? 'next year');
77
		$now = new DateTimeImmutable();
78
79
		$isDoneAiring = $now > $endAirDate;
80
		$isCurrentlyAiring = ($now > $startAirDate) && ! $isDoneAiring;
81
82
		switch (true)
83
		{
84
			case $isCurrentlyAiring:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
85
				return AnimeAiringStatus::AIRING;
86
87
			case $isDoneAiring:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
88
				return AnimeAiringStatus::FINISHED_AIRING;
89
90
			default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
91
				return AnimeAiringStatus::NOT_YET_AIRED;
92
		}
93
	}
94
	
95
	/**
96
	 * Get the name and logo for the streaming service of the current link
97
	 *
98
	 * @param string $hostname
0 ignored issues
show
Documentation introduced by
Should the type for parameter $hostname not be null|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
99
	 * @return array
100
	 */
101
	protected static function getServiceMetaData(string $hostname = null): array
102
	{
103
		switch($hostname)
104
		{
105
			case 'www.crunchyroll.com':
106
				return [
107
					'name' => 'Crunchyroll',
108
					'link' => true,
109
					'logo' => '<svg class="streaming-logo" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><g fill="#F78B24" fill-rule="evenodd"><path d="M22.549 49.145c-.815-.077-2.958-.456-3.753-.663-6.873-1.79-12.693-6.59-15.773-13.009C1.335 31.954.631 28.807.633 24.788c.003-4.025.718-7.235 2.38-10.686 1.243-2.584 2.674-4.609 4.706-6.66 3.8-3.834 8.614-6.208 14.067-6.936 1.783-.239 5.556-.161 7.221.148 3.463.642 6.571 1.904 9.357 3.797 5.788 3.934 9.542 9.951 10.52 16.861.21 1.48.332 4.559.19 4.816-.077.14-.117-.007-.167-.615-.25-3.015-1.528-6.66-3.292-9.388C40.253 7.836 30.249 4.32 20.987 7.467c-7.15 2.43-12.522 8.596-13.997 16.06-.73 3.692-.51 7.31.658 10.882a21.426 21.426 0 0 0 13.247 13.518c1.475.515 3.369.944 4.618 1.047 1.496.122 1.119.239-.727.224-1.006-.008-2.013-.032-2.237-.053z"></path><path d="M27.685 46.1c-7.731-.575-14.137-6.455-15.474-14.204-.243-1.41-.29-4.047-.095-5.345 1.16-7.706 6.97-13.552 14.552-14.639 1.537-.22 4.275-.143 5.746.162 1.28.266 2.7.737 3.814 1.266l.865.411-.814.392c-2.936 1.414-4.748 4.723-4.323 7.892.426 3.173 2.578 5.664 5.667 6.56 1.112.322 2.812.322 3.925 0 1.438-.417 2.566-1.1 3.593-2.173.346-.362.652-.621.68-.576.027.046.106.545.176 1.11.171 1.395.07 4.047-.204 5.371-.876 4.218-3.08 7.758-6.463 10.374-3.2 2.476-7.434 3.711-11.645 3.399z"></path></g></svg>'
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 1311 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
110
				];
111
				
112
			case 'www.funimation.com':
113
				return [
114
					'name' => 'Funimation',
115
					'link' => true,
116
					'logo' => '<svg class="streaming-logo" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><path d="M24.066.017a24.922 24.922 0 0 1 13.302 3.286 25.098 25.098 0 0 1 7.833 7.058 24.862 24.862 0 0 1 4.207 9.575c.82 4.001.641 8.201-.518 12.117a24.946 24.946 0 0 1-4.868 9.009 24.98 24.98 0 0 1-7.704 6.118 24.727 24.727 0 0 1-10.552 2.718A24.82 24.82 0 0 1 13.833 47.3c-5.815-2.872-10.408-8.107-12.49-14.25-2.162-6.257-1.698-13.375 1.303-19.28C5.483 8.07 10.594 3.55 16.602 1.435A24.94 24.94 0 0 1 24.066.017zm-8.415 33.31c.464 2.284 1.939 4.358 3.99 5.48 2.174 1.217 4.765 1.444 7.202 1.181 2.002-.217 3.986-.992 5.455-2.397 1.173-1.151 2.017-2.648 2.33-4.267-1.189-.027-2.378 0-3.566-.03-.568.082-1.137-.048-1.705.014-1.232.012-2.465.003-3.697-.01-.655.066-1.309-.035-1.963.013-1.166-.053-2.334.043-3.5-.025-1.515.08-3.03-.035-4.546.042z" fill="#411299" fill-rule="evenodd"></path></svg>'
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 895 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
117
				];
118
				
119
			case 'www.hulu.com':
120
				return [
121
					'name' => 'Hulu',
122
					'link' => true,
123
					'logo' => '<svg class="streaming-logo" viewBox="0 0 34 50" xmlns="http://www.w3.org/2000/svg"><path d="M22.222 13.889h-11.11V0H0v50h11.111V27.778c0-1.39 1.111-2.778 2.778-2.778h5.555c1.39 0 2.778 1.111 2.778 2.778V50h11.111V25c0-6.111-5-11.111-11.11-11.111z" fill="#8BC34A" fill-rule="evenodd"></path></svg>'
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 313 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
124
				];
125
				
126
			// Default to Netflix, because the API links are broken, 
127
			// and there's no other real identifier for Netflix
128
			default:
129
				return [
130
					'name' => 'Netflix',
131
					'link' => false,
132
					'logo' => '<svg class="streaming-logo" viewBox="0 0 26 50" xmlns="http://www.w3.org/2000/svg"><path d="M.057.258C2.518.253 4.982.263 7.446.253c2.858 7.76 5.621 15.556 8.456 23.324.523 1.441 1.003 2.897 1.59 4.312.078-9.209.01-18.42.034-27.631h7.763v46.36c-2.812.372-5.637.627-8.457.957-1.203-3.451-2.396-6.902-3.613-10.348-1.796-5.145-3.557-10.302-5.402-15.428.129 8.954.015 17.912.057 26.871-2.603.39-5.227.637-7.815 1.119C.052 33.279.06 16.768.057.258z" fill="#E21221" fill-rule="evenodd"></path></svg>'
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 510 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
133
				];
134
		}
135
	}
136
	
137
	/**
138
	 * Reorganize streaming links
139
	 *
140
	 * @param array $included
141
	 * @return array
142
	 */
143
	public static function parseStreamingLinks(array $included): array
144
	{
145
		if ( ! array_key_exists('streamingLinks', $included))
146
		{
147
			return [];
148
		}
149
		
150
		$links = [];
151
		
152 View Code Duplication
		foreach ($included['streamingLinks'] as $streamingLink)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
153
		{
154
			$host = parse_url($streamingLink['url'], \PHP_URL_HOST);
155
			
156
			$links[] = [
157
				'meta' => static::getServiceMetaData($host),
0 ignored issues
show
Security Bug introduced by
It seems like $host defined by parse_url($streamingLink['url'], \PHP_URL_HOST) on line 154 can also be of type false; however, Aviat\AnimeClient\API\Kitsu::getServiceMetaData() does only seem to accept null|string, 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...
158
				'link' => $streamingLink['url'],
159
				'subs' => $streamingLink['subs'],
160
				'dubs' => $streamingLink['dubs']
161
			];
162
		}
163
		
164
		return $links;
165
	}
166
	
167
	/**
168
	 * Reorganize streaming links for the current list item
169
	 *
170
	 * @param array $included
171
	 * @return array
172
	 */
173
	public static function parseListItemStreamingLinks(array $included, string $animeId): array
174
	{
175
		// Anime lists have a different structure to search through
176
		if (array_key_exists('anime', $included) && ! array_key_exists('streamingLinks', $included))
177
		{
178
			$links = [];
179
			$anime = $included['anime'][$animeId];
180
181
			if (count($anime['relationships']['streamingLinks']) > 0)
182
			{
183 View Code Duplication
				foreach ($anime['relationships']['streamingLinks'] as $streamingLink)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
				{
185
					$host = parse_url($streamingLink['url'], \PHP_URL_HOST);
186
187
					$links[] = [
188
						'meta' => static::getServiceMetaData($host),
0 ignored issues
show
Security Bug introduced by
It seems like $host defined by parse_url($streamingLink['url'], \PHP_URL_HOST) on line 185 can also be of type false; however, Aviat\AnimeClient\API\Kitsu::getServiceMetaData() does only seem to accept null|string, 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...
189
						'link' => $streamingLink['url'],
190
						'subs' => $streamingLink['subs'],
191
						'dubs' => $streamingLink['dubs']
192
					];
193
				}
194
			}
195
			
196
			return $links;
197
		}
198
		
199
		return [];
200
	}
201
202
	/**
203
	 * Filter out duplicate and very similar names from
204
	 *
205
	 * @param array $data The 'attributes' section of the api data response
206
	 * @return array List of alternate titles
207
	 */
208
	public static function filterTitles(array $data): array
209
	{
210
		// The 'canonical' title is always returned
211
		$valid = [$data['canonicalTitle']];
212
213
		if (array_key_exists('titles', $data))
214
		{
215
			foreach($data['titles'] as $alternateTitle)
216
			{
217
				if (self::titleIsUnique($alternateTitle, $valid))
218
				{
219
					$valid[] = $alternateTitle;
220
				}
221
			}
222
		}
223
224
		return $valid;
225
	}
226
227
	/**
228
	 * Determine if an alternate title is unique enough to list
229
	 *
230
	 * @param string $title
0 ignored issues
show
Documentation introduced by
Should the type for parameter $title not be null|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
231
	 * @param array $existingTitles
232
	 * @return bool
233
	 */
234
	private static function titleIsUnique(string $title = null, array $existingTitles): bool
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
235
	{
236
		if (empty($title))
237
		{
238
			return false;
239
		}
240
241
		foreach($existingTitles as $existing)
242
		{
243
			$isSubset = stripos($existing, $title) !== FALSE;
244
			$diff = levenshtein($existing, $title);
245
			$onlydifferentCase = (mb_strtolower($existing) === mb_strtolower($title));
246
247
			if ($diff < 3 || $isSubset || $onlydifferentCase)
248
			{
249
				return false;
250
			}
251
		}
252
253
		return true;
254
	}
255
}