added on local at 2026-07-01 16:01:32
| 1 | package MODS::ABForge::UAParser; | |
| 2 | #====================================================================== | |
| 3 | # ABForge -- minimal User-Agent string parser. No CPAN deps. | |
| 4 | # Catches the top 95% of real-world UA strings into: | |
| 5 | # { browser, browser_version, os, os_version, device } | |
| 6 | #====================================================================== | |
| 7 | use strict; | |
| 8 | use warnings; | |
| 9 | ||
| 10 | sub new { bless {}, shift } | |
| 11 | ||
| 12 | sub parse { | |
| 13 | my ($self, $ua) = @_; | |
| 14 | $ua //= ''; | |
| 15 | my $r = { browser => '', browser_version => '', os => '', os_version => '', device => 'desktop' }; | |
| 16 | ||
| 17 | # OS | |
| 18 | if ($ua =~ /Windows NT 10/i) { $r->{os} = 'Windows'; $r->{os_version} = '10/11'; } | |
| 19 | elsif ($ua =~ /Windows NT ([\d.]+)/i) { $r->{os} = 'Windows'; $r->{os_version} = $1; } | |
| 20 | elsif ($ua =~ /Mac OS X ([\d_]+)/i) { $r->{os} = 'macOS'; ($r->{os_version} = $1) =~ tr/_/./; } | |
| 21 | elsif ($ua =~ /iPhone OS ([\d_]+)/i) { $r->{os} = 'iOS'; ($r->{os_version} = $1) =~ tr/_/./; $r->{device} = 'mobile'; } | |
| 22 | elsif ($ua =~ /iPad/i && $ua =~ /OS ([\d_]+)/) { $r->{os} = 'iOS'; ($r->{os_version} = $1) =~ tr/_/./; $r->{device} = 'tablet'; } | |
| 23 | elsif ($ua =~ /Android ([\d.]+)/i) { $r->{os} = 'Android'; $r->{os_version} = $1; $r->{device} = ($ua =~ /Mobile/i) ? 'mobile' : 'tablet'; } | |
| 24 | elsif ($ua =~ /CrOS/i) { $r->{os} = 'ChromeOS'; } | |
| 25 | elsif ($ua =~ /Linux/i) { $r->{os} = 'Linux'; } | |
| 26 | ||
| 27 | # Browser (order matters: Edge before Chrome before Safari, OPR before Chrome) | |
| 28 | if ($ua =~ /Edg(?:e|A|iOS)?\/([\d.]+)/i) { $r->{browser} = 'Edge'; $r->{browser_version} = $1; } | |
| 29 | elsif ($ua =~ /OPR\/([\d.]+)/i) { $r->{browser} = 'Opera'; $r->{browser_version} = $1; } | |
| 30 | elsif ($ua =~ /SamsungBrowser\/([\d.]+)/i) { $r->{browser} = 'Samsung'; $r->{browser_version} = $1; } | |
| 31 | elsif ($ua =~ /YaBrowser\/([\d.]+)/i) { $r->{browser} = 'Yandex'; $r->{browser_version} = $1; } | |
| 32 | elsif ($ua =~ /Firefox\/([\d.]+)/i) { $r->{browser} = 'Firefox'; $r->{browser_version} = $1; } | |
| 33 | elsif ($ua =~ /CriOS\/([\d.]+)/i) { $r->{browser} = 'Chrome'; $r->{browser_version} = $1; } | |
| 34 | elsif ($ua =~ /Chrome\/([\d.]+)/i) { $r->{browser} = 'Chrome'; $r->{browser_version} = $1; } | |
| 35 | elsif ($ua =~ /Version\/([\d.]+).*Safari/i) { $r->{browser} = 'Safari'; $r->{browser_version} = $1; } | |
| 36 | elsif ($ua =~ /Safari/i) { $r->{browser} = 'Safari'; } | |
| 37 | elsif ($ua =~ /MSIE ([\d.]+)/i) { $r->{browser} = 'IE'; $r->{browser_version} = $1; } | |
| 38 | elsif ($ua =~ /Trident.*rv:([\d.]+)/i) { $r->{browser} = 'IE'; $r->{browser_version} = $1; } | |
| 39 | ||
| 40 | # Device override | |
| 41 | if ($ua =~ /bot|crawl|spider|google|bing|yahoo|baidu|duckduck|slurp|yandexbot|sogou|exabot|facebookexternalhit|monitor/i) { | |
| 42 | $r->{device} = 'bot'; | |
| 43 | } elsif ($r->{device} eq 'desktop') { | |
| 44 | if ($ua =~ /iPad|Tablet/i) { $r->{device} = 'tablet'; } | |
| 45 | elsif ($ua =~ /Mobi|iPhone|Android.*Mobile/i) { $r->{device} = 'mobile'; } | |
| 46 | } | |
| 47 | ||
| 48 | return $r; | |
| 49 | } | |
| 50 | ||
| 51 | 1; |