Passed
Push — main ( d3029f...a35a1e )
by Will
14:00
created

apps::get()   F

Complexity

Conditions 53
Paths 1

Size

Total Lines 374
Code Lines 287

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 344
CRAP Score 53

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 53
eloc 287
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 374
ccs 344
cts 345
cp 0.9971
crap 53
rs 3.3333

How to fix   Long Method    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
2
declare(strict_types = 1);
3
namespace hexydec\agentzero;
4
5
class apps {
6
7
	/**
8
	 * Generates a configuration array for matching apps
9
	 * 
10
	 * @return array<string,props> An array with keys representing the string to match, and values a props object defining how to generate the match and which properties to set
11
	 */
12 52
	public static function get() : array {
13 1
		$fn = [
14 1
			'appslash' => function (string $value, int $i, array $tokens, string $match) : array {
15 52
				if (\mb_stripos($value, 'AppleWebKit') === false && !\str_contains($value, '://')) {
16 27
					$parts = \explode('/', $value, 4);
17 27
					$offset = isset($parts[2]) ? 1 : 0;
18 27
					$app = \str_replace('GooglePlayStore ', '', $parts[0 + $offset]);
19 27
					if (\mb_stripos($app, \rtrim($match, '/')) !== false) { // check the match is before the slash
20 27
						return [
21 27
							'app' => self::getApp($app),
22 27
							'appname' => \trim($app, '[]'),
23 27
							'appversion' => $parts[1 + $offset] ?? null
24 27
						];
25
					}
26
				}
27 41
				return [];
28 1
			},
29 1
		];
30 3
		return [
31 3
			'com.google.' => new props('start', function (string $value) : array {
32 1
				$parts = \explode('/', $value, 3);
33 1
				return [
34 1
					'type' => 'human',
35 1
					'app' => self::getApp($parts[0]),
36 1
					'appname' => $parts[0],
37 1
					'appversion' => $parts[1] ?? null
38 1
				];
39 3
			}),
40 3
			'OcIdWebView' => new props('exact', function (string $value) : array {
41 1
				$data = [
42 1
					'app' => 'Google App Web View',
43 1
					'appname' => $value
44 1
				];
45 1
				return $data;
46 3
			}),
47 3
			'Instagram' => new props('any', function (string $value, int $i, array $tokens) : array {
48 8
				$parts = \explode(' ', $value, 4);
49 8
				$data = [
50 8
					'type' => 'human',
51 8
					'app' => 'Instagram',
52 8
					'appname' => 'Instagram',
53 8
					'appversion' => $parts[1] ?? null,
54 8
					'platform' => empty($parts[2]) ? null : platforms::getPlatform($parts[2])
55 8
				];
56 8
				foreach (\array_slice($tokens, $i + 1) AS $key => $item) {
57 8
					$ipad = null;
58 8
					if (($ipados = \str_starts_with($item, 'iPadOS')) || \str_starts_with($item, 'iOS ')) {
59 2
						$data['kernel'] = 'Linux';
60 2
						$data['platform'] = 'iOS';
61 2
						$data['platformversion'] = \str_replace('_', '.', \mb_substr($item, $ipados ? 7 : 4));
62 2
						$data['density'] = \floatval(\mb_substr($item, 6));
63 8
					} elseif (($iphone = \str_starts_with($item, 'iPhone')) || ($ipad = \str_starts_with($item, 'iPad')) || \str_starts_with($item, 'iPod')) {
64 2
						$data['vendor'] = 'Apple';
65 2
						$data['category'] = $ipad ? 'tablet' : 'mobile';
66 2
						$data['device'] = $iphone ? 'iPhone' : ($ipad ? 'iPad' : 'iPod');
67 2
						$data['model'] = \str_replace(',', '.', \mb_substr($item, $iphone ? 6 : 4));
68 2
						$data['architecture'] = 'Arm';
69 2
						$data['bits'] = 64;
70 8
					} elseif (\str_starts_with($item, 'scale=')) {
71 2
						$data['density'] = \floatval(\mb_substr($item, 6));
72 8
					} elseif (\str_ends_with($item, 'dpi')) {
73 7
						$data['dpi'] = \intval(\mb_substr($item, 0, -3));
74 8
					} elseif (\str_contains($item, 'x') && \strspn($item, '0123456789x') === \strlen($item)) {
75 8
						list($data['width'], $data['height']) = \array_map('intval', \explode('x', $item, 2));
76
77
						// get device when the UA string starts with "Instagram"
78 8
						if ($i === 0 && !isset($data['vendor'])) {
79 1
							$device = [
80 1
								\trim(\mb_strstr($tokens[$key + 2] ?? '', '/') ?: $tokens[$key + 2] ?? '', '/ '), // sometimes the vendor name has a / with the correct name after
81 1
								$tokens[$key + 3] ?? null
82 1
							];
83
84
							// remove duplicated name
85 1
							if ($device[1] !== null && \mb_stripos($device[1], $device[0]) === 0) {
86 1
								unset($device[0]);
87
							}
88 1
							$data = \array_merge($data, devices::getDevice(\implode(' ', \array_filter($device))));
89 1
							break;
90
						}
91
					}
92
				}
93 8
				return $data;
94 3
			}),
95 3
			'imoAndroid/' => new props('start', fn (string $value, int $i, array $tokens) : array => \array_merge(
96 3
				isset($tokens[$i + 3]) ? devices::getDevice($tokens[$i + 3]) : [],
97 3
				[
98 3
					'app' => 'imo',
99 3
					'appname' => 'imoAndroid',
100 3
					'appversion' => ($ver = \mb_strstr($value, '/')) === false ? $value : \ltrim($ver, '/'),
101 3
					'platform' => 'Android',
102 3
					'platformversion' => $tokens[$i + 1] ?? null,
103 3
					'cpu' => $tokens[$i + 11] ?? null,
104 3
					'vendor' => isset($tokens[$i + 4]) ? devices::getVendor($tokens[$i + 4]) : null
105 3
				]
106 3
			)),
107 3
			'GSA/' => new props('any', $fn['appslash']),
108 3
			'DuckDuckGo/' => new props('start', $fn['appslash']),
109 3
			'FlipboardProxy/' => new props('start', $fn['appslash']),
110 3
			'Emacs/' => new props('start', $fn['appslash']),
111 3
			'AndroidDownloadManager/' => new props('start', $fn['appslash']),
112 3
			'Zoom ' => new props('start', fn (string $value) : array => [
113 2
				'app' => 'Zoom',
114 2
				'appname' => 'Zoom',
115 2
				'appversion' => \mb_substr($value, 5)
116 2
			]),
117 3
			'Reddit/' => new props('start', function (string $value, int $i, array $tokens) : array {
118 1
				$os = !empty($tokens[$i+2]) ? \explode('/', $tokens[$i+2], 2) : null;
119 1
				$parts = !empty($os[1]) ? \explode(' ', $os[1], 3) : null;
120 1
				return [
121 1
					'type' => 'human',
122 1
					'app' => 'Reddit',
123 1
					'appname' => 'Reddit',
124 1
					'appversion' => $value === 'Reddit/Version' ? (\mb_strstr($tokens[$i+1], '/', true) ?: null) : \mb_substr($value, 7),
125 1
					'platform' => $parts[0] ?? null,
126 1
					'platformversion' => $tokens[$i+3] ?? null
127 1
				];
128 3
			}),
129 3
			'Pinterest for ' => new props('start', fn (string $value) : array => [
130 1
				'type' => 'human',
131 1
				'app' => 'Pinterest',
132 1
				'appname' => 'Pinterest',
133 1
				'appversion' => \explode('/', $value, 3)[1],
134 1
				'platform' => \mb_substr($value, 14, \mb_strpos($value, '/') - 14)
135 1
			]),
136 3
			'OculusBrowser/' => new props('start', $fn['appslash']),
137 3
			'BaiduBrowser/' => new props('start', $fn['appslash']),
138 3
			'bdhonorbrowser/' => new props('start', $fn['appslash']),
139 3
			'YaBrowser/' => new props('start', $fn['appslash']),
140 3
			'choqok/' => new props('start', $fn['appslash']),
141 3
			'Quora ' => new props('start', fn (string $value) : array => [
142 1
				'type' => 'human',
143 1
				'app' => 'Quora',
144 1
				'appname' => 'Quora',
145 1
				'appversion' => \explode(' ', $value, 3)[1]
146 1
			]),
147 3
			'AmazonKidsBrowser/' => new props('start', $fn['appslash']),
148 3
			'whisper/' => new props('start', $fn['appslash']),
149 3
			'Teams/' => new props('start', $fn['appslash']),
150 3
			'Viber/' => new props('start', $fn['appslash']),
151 3
			'MXPlayer/' => new props('start', fn (string $value) : array => [
152 1
				'app' => 'MXPlayer',
153 1
				'appname' => 'MXPlayer',
154 1
				'appversion' => \explode('/', $value, 3)[1] ?: null
155 1
			]),
156 3
			'Todoist-Android/' => new props('start', fn (string $value) : array => [
157 1
				'type' => 'human',
158 1
				'platform' => 'Android',
159 1
				'app' => 'Todoist',
160 1
				'appname' => 'Todoist-Android',
161 1
				'appversion' => \explode('/', $value, 3)[1] ?: null
162 1
			]),
163 3
			'Trello for Android/' => new props('start', fn (string $value) : array => [
164 1
				'type' => 'human',
165 1
				'platform' => 'Android',
166 1
				'app' => 'Trello',
167 1
				'appname' => 'Trello for Android',
168 1
				'appversion' => \explode('/', $value, 3)[1] ?: null
169 1
			]),
170 3
			'iPad PurpleMashApp' => new props('start', fn (string $value) : array => [
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

170
			'iPad PurpleMashApp' => new props('start', fn (/** @scrutinizer ignore-unused */ string $value) : array => [

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
171 1
				'type' => 'human',
172 1
				'platform' => 'iOS',
173 1
				'vendor' => 'Apple',
174 1
				'device' => 'iPad',
175 1
				'app' => 'Purple Mash',
176 1
				'appname' => 'PurpleMashApp'
177 1
			]),
178 3
			'Instapaper for Android ' => new props('start', fn (string $value) : array => [
179 1
				'type' => 'human',
180 1
				'platform' => 'Android',
181 1
				'app' => 'Instapaper',
182 1
				'appname' => 'Instapaper',
183 1
				'appversion' => \mb_substr($value, \mb_strrpos($value, ' ') + 1)
184 1
			]),
185 3
			'Instapaper' => new props('start', fn (string $value, int $i, array $tokens, string $match) => \array_merge([
186 3
				'type' => 'human'
187 3
			], $fn['appslash']($value, $i, $tokens, $match))),
188 3
			'Player/LG' => new props('start', function (string $value, int $i, array $tokens) : ?array {
189 1
				if (\str_starts_with($tokens[$i+1], 'Player ')) {
190 1
					$parts = \explode(' ', $tokens[$i+1]);
191 1
					$device = $i === 1 ? \explode('/', $tokens[0]) : null;
192 1
					return [
193 1
						'type' => 'human',
194 1
						'vendor' => 'LG',
195 1
						'device' => $device[0] ?? null,
196 1
						'model' => $device[1] ?? null,
197 1
						'app' => 'LG Player',
198 1
						'appname' => 'LG Player',
199 1
						'appversion' => $parts[1] ?? null,
200 1
						'platform' => $parts[3] ?? null,
201 1
						'platformversion' => $parts[4] ?? null
202 1
					];
203
				}
204
				return null;
205 3
			}),
206 3
			'RobloxApp/' => new props('any', $fn['appslash']),
207 3
			'nu.nl/' => new props('any', fn (string $value) : array => [
208 1
				'app' => 'nu.nl',
209 1
				'appname' => 'nu.nl',
210 1
				'appversion' => \mb_substr($value, 6)
211 1
			]),
212 3
			'Google Web Preview' => new props('start', $fn['appslash']),
213 3
			'MicroMessenger/' => new props('start', $fn['appslash']),
214 3
			'MicroMessenger Weixin QQ' => new props('start', fn () : array => [
215 2
				'app' => 'WeChat',
216 2
				'appname' => 'MicroMessenger'
217 2
			]),
218 3
			'weibo' => new props('any', function (string $value) : array {
219 1
				$data = [
220 1
					'app' => 'Weibo',
221 1
					'appname' => 'Weibo'
222 1
				];
223 1
				$parts = \explode('_', $value);
224 1
				foreach ($parts AS $i => $item) {
225 1
					if (\mb_stripos($item, 'Weibo') !== false) {
226 1
						$data['appname'] = $item;
227 1
						$data['appversion'] = $parts[$i + (\strspn($parts[$i + 1] ?? '', '0123456789', 0, 1) === 1 ? 1 : 2)] ?? null;
228 1
						break;
229
					}
230
				}
231 1
				return $data;
232 3
			}),
233
234
			// special parser for Facebook app because it is completely different to any other
235 3
			'FBAN/' => new props('any', function (string $value) : array {
236 4
				$map = [
237 4
					'FBAN/MessengerLiteForiOS' => [
238 4
						'type' => 'human',
239 4
						'app' => 'Facebook Messenger',
240 4
						'platform' => 'iOS'
241 4
					],
242 4
					'FBAN/FB4A' => [
243 4
						'type' => 'human',
244 4
						'app' => 'Facebook',
245 4
						'platform' => 'Android'
246 4
					],
247 4
					'FBAN/FBIOS' => [
248 4
						'type' => 'human',
249 4
						'app' => 'Facebook',
250 4
						'platform' => 'iOS'
251 4
					],
252 4
					'FBAN/FB4FireTV' => [
253 4
						'type' => 'human',
254 4
						'category' => 'tv',
255 4
						'app' => 'Facebook',
256 4
						'platform' => 'Android'
257 4
					],
258 4
					'FBAN/MessengerDesktop' => [
259 4
						'type' => 'human',
260 4
						'category' => 'desktop',
261 4
						'app' => 'Facebook Messenger'
262 4
					],
263 4
					'FacebookCanvasDesktop FBAN/GamesWindowsDesktopApp' => [
264 4
						'type' => 'human',
265 4
						'platform' => 'Windows',
266 4
						'category' => 'desktop',
267 4
						'app' => 'Facebook Gamesroom'
268 4
					]
269 4
				];
270 4
				return \array_merge([
271 4
					'type' => 'human',
272 4
					'app' => 'Facebook',
273 4
					'appname' => \explode('/', $value, 2)[1]
274 4
				], $map[$value] ?? []);
275 3
			}),
276 3
			'FB_IAB/' => new props('start', fn (string $value) : array => [
277 7
				'app' => 'Facebook',
278 7
				'appname' => \mb_substr($value, 7)
279 7
			]),
280 3
			'FBAV/' => new props('start', fn (string $value) : array => [
281 9
				'appversion' => \mb_substr($value, 5)
282 9
			]),
283 3
			'FBMF/' => new props('start', fn (string $value) : array => [
284 2
				'vendor' => devices::getVendor(\mb_substr($value, 5))
285 2
			]),
286 3
			'FBDV/' => new props('start', fn (string $value) : array => devices::getDevice(\mb_substr($value, 5))),
287 3
			'FBMD/' => new props('start', fn (string $value) : array => [
288 3
				'model' => \mb_substr($value, 5)
289 3
			]),
290 3
			'FBDM/' => new props('start', function (string $value) : array {
291 1
				$data = [];
292 1
				foreach (\explode(',', \trim(\mb_substr($value, 5), '{}')) AS $item) {
293 1
					$parts = \explode('=', $item);
294 1
					if (!empty($parts[1])) {
295 1
						if (\is_numeric($parts[1])) {
296 1
							$parts[1] = \str_contains($parts[1], '.') ? \floatval($parts[1]) : \intval($parts[1]);
297
						}
298 1
						$data[$parts[0]] = $parts[1];
299
					}
300
				}
301 1
				return $data;
302 3
			}),
303 3
			'width=' => new props('start', fn (string $value) : array => [
304 2
				'width' => \intval(\mb_substr($value, 6))
305 2
			]),
306 3
			'height=' => new props('start', fn (string $value) : array => [
307 2
				'height' => \intval(\mb_substr($value, 7))
308 2
			]),
309 3
			'dpi=' => new props('start', fn (string $value) : array => [
310 1
				'dpi' => \mb_substr($value, 4)
311 1
			]),
312 3
			'FBSN/' => new props('start', fn (string $value) : array => [
313 3
				'platform' => \mb_substr($value, 5)
314 3
			]),
315 3
			'FBSV' => new props('start', fn (string $value) : array => [
316 3
				'platformversion' => \mb_substr($value, 5)
317 3
			]),
318 3
			'isDarkMode/' => new props('start', function (string $value) : array {
319 1
				$mode = \mb_substr($value, 11);
320 1
				return [
321 1
					'darkmode' => \in_array($mode, ['0', '1'], true) ? \boolval($mode) : null
322 1
				];
323 3
			}),
324 3
			'AppTheme/' => new props('start', fn (string $value) : array => [
325 1
				'darkmode' => \mb_substr($value, 9) === 'dark'
326 1
			]),
327 3
			'NetType/' => new props('start', fn (string $value) : array => [
328 5
				'nettype' => \mb_convert_case(\mb_substr($value, 8), MB_CASE_UPPER)
329 5
			]),
330 3
			'Microsoft Office' => new props('start', function (string $value, int $i, array $tokens) : array {
331 1
				$data = [
332 1
					'type' => 'human'
333 1
				];
334 1
				if (\str_contains($value, '/')) {
335 1
					foreach (\array_slice($tokens, $i + 1) AS $item) {
336 1
						if (\str_starts_with($item, 'Microsoft ')) {
337 1
							$parts = \explode(' ', $item);
338 1
							$data['app'] = $parts[0].' '.$parts[1];
339 1
							$data['appname'] = $parts[0].' '.$parts[1];
340 1
							if (isset($parts[2])) {
341 1
								$data['appversion'] = $parts[2];
342
							}
343 1
							break;
344
						}
345
					}
346 1
					if (!isset($data['app'])) {
347 1
						$parts = \explode('/', $value, 2);
348 1
						$data['app'] = $parts[0];
349 1
						$data['appname'] = $parts[0];
350 1
						if (!isset($data['appversion'])) {
351 1
							$data['appversion'] = $parts[1];
352
						}
353
					}
354
				} else {
355 1
					$parts = \explode(' ', $value);
356 1
					$data['app'] = \rtrim($parts[0].' '.($parts[1] ?? '').' '.($parts[2] ?? ''));
357 1
					$data['appname'] = $value;
358 1
					$data['appversion'] = $parts[3] ?? null;
359
				}
360 1
				return $data;
361 3
			}),
362
363
			// TikTok
364 3
			'AppName/' => new props('start', fn(string $value) : array => [
365 3
				'app' => $value === 'AppName/musical_ly' ? 'TikTok' : \mb_substr($value, 8),
366 3
				'appname' => \mb_substr($value, 8)
367 3
			]),
368 3
			'app_version/' => new props('start', fn(string $value) : array => [
369 3
				'appversion' => \mb_substr($value, 12)
370 3
			]),
371 3
			'AppVersion/' => new props('any', fn(string $value) : array => [
372 2
				'appversion' => \explode('/', $value, 2)[1]
373 2
			]),
374 3
			'musical_ly' => new props('start', fn(string $value) : array => [
375 2
				'app' => 'TikTok',
376 2
				'appname' => 'musical_ly',
377 2
				'appversion' => \str_replace('_', '.', \mb_substr(\explode(' ', $value, 2)[0], 11))
378 2
			]),
379 3
			'Android App' => new props('any', [
380 3
				'type' => 'human',
381 3
				'platform' => 'Android'
382 3
			]),
383
				
384
			// generic
385 3
			'App' => new props('any', $fn['appslash'])
386 3
		];
387
	}
388
389 27
	public static function getApp(string $value) : string {
390 27
		$map = [
391 27
			'YaApp_iOS' => 'Yandex',
392 27
			'YaApp_Android' => 'Yandex',
393 27
			'YaSearchApp' => 'Yandex',
394 27
			'YaBrowser' => 'Yandex',
395 27
			'LinkedInApp' => 'LinkedIn',
396 27
			'[LinkedInApp]' => 'LinkedIn',
397 27
			'GoogleApp' => 'Google',
398 27
			'com.zhiliaoapp.musically' => 'TikTok',
399 27
			'com.google.android.apps.searchlite' => 'Google',
400 27
			'com.google.android.googlequicksearchbox' => 'Google',
401 27
			'com.google.android.gms' => 'Google',
402 27
			'com.google.GoogleMobile' => 'Google',
403 27
			'com.google.Maps' => 'Google Maps',
404 27
			'com.google.photos' => 'Google Photos',
405 27
			'com.google.ios.youtube' => 'YouTube',
406 27
			'com.google.android.youtube' => 'YouTube',
407 27
			'AlohaBrowserApp' => 'Aloha',
408 27
			'OculusBrowser' => 'Oculus Browser',
409 27
			'AndroidDownloadManager' => 'Android Download Manager',
410 27
			'imoAndroid' => 'imo',
411 27
			'MicroMessenger' => 'WeChat',
412 27
			'GSA' => 'Google',
413 27
			'baiduboxapp' => 'Baidu',
414 27
			'lite baiduboxapp' => 'Baidu',
415 27
			'baidubrowser' => 'Baidu',
416 27
			'bdhonorbrowser' => 'Baidu',
417 27
			'RobloxApp' => 'Roblox'
418 27
		];
419 27
		if (isset($map[$value])) {
420 13
			return $map[$value];
421 17
		} elseif (($pos = \mb_strrpos($value, '.')) !== false) {
422 3
			return \mb_substr($value, $pos + 1);
423
		}
424 15
		return $value;
425
	}
426
}