Current section
Files
Jump to
Current section
Files
lib/os_detect.ex
defmodule OsDetect do
@moduledoc """
A fast and User Agent parser which only returns the operating system.
"""
alias OsDetect.Result
@doc """
Parse the operating system, browser and platform type from a user-agent string.
## Examples
iex> OsDetect.parse("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36")
%OsDetect.Result{browser: "chrome", os: "mac os", type: :desktop}
iex> OsDetect.parse("Mozilla/5.0 (Linux; Android 10; Pixel 3 XL Build/QQ3A.200605.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 GSA/11.18.11.21.arm64")
%OsDetect.Result{browser: "webview", os: "android", type: :mobile}
iex> OsDetect.parse("Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML}, like Gecko) FxiOS/7.5b3349 Mobile/14F89 Safari/603.2.4")
%OsDetect.Result{browser: "firefox", os: "ios", type: :mobile}
"""
@spec parse(user_agent :: binary()) :: Result.t()
def parse(<<"Python/", _::binary()>>), do: %Result{type: :bot}
def parse(<<"curl/", _::binary()>>), do: %Result{type: :bot}
def parse(<<"akka-http/", _::binary()>>), do: %Result{type: :bot}
def parse(<<"okhttp/", _::binary()>>), do: %Result{type: :bot}
def parse(<<"Apache", _::binary()>>), do: %Result{type: :bot}
def parse("site24x7"), do: %Result{type: :bot}
def parse(user_agent) when is_binary(user_agent) and byte_size(user_agent) >= 10 do
with {:ok, acc, rest} <- extract_os(user_agent),
{:ok, acc, rest} <- extract_browser(acc, rest),
{:ok, acc} <- complete(acc, rest) do
acc
else
{:done, result} -> result
{:error, :bot} -> %Result{type: :bot}
_ -> %Result{type: :unknown}
end
end
def parse(_), do: %Result{type: :unknown}
defp extract_os(<<"Mozilla/5.0 (Mobile; Windows Phone", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "windows phone"}, rest}
defp extract_os(<<"Mozilla/5.0 (Mobile;", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "firefox os"}, rest}
defp extract_os(<<"Mozilla/5.0 (Tablet;", rest::binary()>>),
do: {:ok, %Result{type: :tablet, os: "firefox os"}, rest}
# skip ahead
defp extract_os(<<"Mozilla/4.0 ", rest::binary()>>), do: extract_os(rest)
defp extract_os(<<"Mozilla/5.0 ", rest::binary()>>), do: extract_os(rest)
defp extract_os(<<"Android", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "android", browser: "webview"}, rest}
defp extract_os(<<"android", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "android", browser: "webview"}, rest}
defp extract_os(<<"diordnA", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "android", browser: "webview"}, rest}
defp extract_os(<<"Dalvik", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "android", browser: "webview"}, rest}
defp extract_os(<<"iPhone", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "ios", browser: "webview"}, rest}
defp extract_os(<<"iPad", rest::binary()>>),
do: {:ok, %Result{type: :tablet, os: "ios", browser: "webview"}, rest}
defp extract_os(<<"iPod", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "ios", browser: "webview"}, rest}
defp extract_os(<<"Macintosh", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "mac os"}, rest}
defp extract_os(<<"Windows Phone", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "windows phone"}, rest}
defp extract_os(<<"Windows Mobile", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "windows phone"}, rest}
defp extract_os(<<"Windows NT", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "windows"}, rest}
defp extract_os(<<"WinNT", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "windows"}, rest}
defp extract_os(<<"Windows 10", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "windows"}, rest}
defp extract_os(<<"Windows; U; Win95;", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "windows"}, rest}
defp extract_os(<<"Windows; U; Win98;", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "windows"}, rest}
defp extract_os(<<"Windows; U; Win 9", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "windows"}, rest}
defp extract_os(<<"Windows 9", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "windows"}, rest}
defp extract_os(<<"IEMobile", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "windows phone", browser: "ie"}, rest}
defp extract_os(<<" moto ", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "android"}, rest}
defp extract_os(<<" Pixel ", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "android"}, rest}
defp extract_os(<<"CrKey", _::binary()>>), do: smart_tv("chromecast")
defp extract_os(<<"Apple TV", _::binary()>>), do: smart_tv("apple tv")
defp extract_os(<<"AppleTV", _::binary()>>), do: smart_tv("apple tv")
defp extract_os(<<"appletv", _::binary()>>), do: smart_tv("apple tv")
defp extract_os(<<"tvOS", _::binary()>>), do: smart_tv("apple tv")
defp extract_os(<<"Roku", _::binary()>>), do: smart_tv("roku")
defp extract_os(<<"roku", _::binary()>>), do: smart_tv("roku")
defp extract_os(<<"PlayStation", _::binary()>>), do: smart_tv("playstation")
defp extract_os(<<"PLAYSTATION", _::binary()>>), do: smart_tv("playstation")
defp extract_os(<<"PS3Application", _::binary()>>), do: smart_tv("playstation")
defp extract_os(<<"SMART-TV", _::binary()>>), do: smart_tv("tizen")
defp extract_os(<<"TSBNetTV", _::binary()>>), do: smart_tv("toshiba")
defp extract_os(<<"GoogleTV", _::binary()>>), do: smart_tv("android tv")
defp extract_os(<<"BenQ", _::binary()>>), do: smart_tv("benq")
defp extract_os(<<" LG SimpleSmart.TV", _::binary()>>), do: smart_tv("lg")
defp extract_os(<<"Philips", _::binary()>>), do: smart_tv("philips")
defp extract_os(<<"TPM1", _::binary()>>), do: smart_tv("philips")
defp extract_os(<<"QM16", _::binary()>>), do: smart_tv("philips")
defp extract_os(<<"NetCast", _::binary()>>), do: smart_tv("netcast")
defp extract_os(<<"Verizon_STB", _::binary()>>), do: smart_tv("verizon stb")
defp extract_os(<<"TiVo", _::binary()>>), do: smart_tv("tivo")
defp extract_os(<<"Funai", _::binary()>>), do: smart_tv("funai")
defp extract_os(<<"Vestel", _::binary()>>), do: smart_tv("vestel")
defp extract_os(<<"SonyCEBrowser", _::binary()>>), do: smart_tv("sony bravia")
defp extract_os(<<"Plex", _::binary()>>), do: smart_tv("plex")
defp extract_os(<<"Zeasn", _::binary()>>), do: smart_tv("zeasn")
defp extract_os(<<"SHIVAKI", _::binary()>>), do: smart_tv("shivaki")
defp extract_os(<<"HbbTV", _::binary()>>), do: smart_tv("hbbtv")
defp extract_os(<<"Web0S", _::binary()>>), do: smart_tv("web os")
defp extract_os(<<"webOS", _::binary()>>), do: smart_tv("web os")
defp extract_os(<<"Tizen", _::binary-size(6), "SmartHub", _::binary()>>),
do: smart_tv("tizen")
defp extract_os(<<"Tizen", _::binary-size(6), "SAMSUNG", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "tizen"}, rest}
defp extract_os(<<"Tizen", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "tizen"}, rest}
defp extract_os(<<"(X11; CrOS", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "chrome os"}, rest}
defp extract_os(<<"(Wayland;", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "linux"}, rest}
defp extract_os(<<"(Darwin; FreeBSD", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "freebsd"}, rest}
defp extract_os(<<"(X11; FreeBSD", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "freebsd"}, rest}
defp extract_os(<<"(X11;", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "linux"}, rest}
defp extract_os(<<"(Linux; U; X11;", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "linux"}, rest}
defp extract_os(<<"OS/2; Warp", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "os/2"}, rest}
defp extract_os(<<"Linux; Ubuntu", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "linux"}, rest}
defp extract_os(<<"Linux x86_64", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "linux"}, rest}
defp extract_os(<<"Linux; x86_64", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "linux"}, rest}
defp extract_os(<<"Linux aarch64", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "linux"}, rest}
defp extract_os(<<"(Unknown; Linux)", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "linux"}, rest}
defp extract_os(<<"[FBAN/FBIOS", _::binary()>>),
do: {:done, %Result{type: :mobile, os: "ios", browser: "webview"}}
defp extract_os(<<"[FBAN/", _::binary()>>),
do: {:done, %Result{type: :mobile, os: "android", browser: "webview"}}
defp extract_os(<<"Commscope", _::binary()>>), do: smart_tv("commscope")
defp extract_os(<<"Hisense", _::binary()>>), do: smart_tv("hisense")
defp extract_os(<<"BRAVIA", _::binary()>>), do: smart_tv("sony bravia")
defp extract_os(<<"VIZIO", _::binary()>>), do: smart_tv("vizio")
defp extract_os(<<"Vizio", _::binary()>>), do: smart_tv("vizio")
defp extract_os(<<"Viera", _::binary()>>), do: smart_tv("panasonic")
defp extract_os(<<"Air TV", _::binary()>>), do: smart_tv("air tv")
defp extract_os(<<"AOC_TV", _::binary()>>), do: smart_tv("aoc tv")
defp extract_os(<<"BrightSign", _::binary()>>), do: smart_tv("brightsign")
defp extract_os(<<"BlackBerry", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "blackberry"}, rest}
defp extract_os(<<"(BB10; ", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "blackberry"}, rest}
defp extract_os(<<"Xbox", _::binary()>>), do: smart_tv("xbox")
defp extract_os(<<"Sailfish", rest::binary()>>),
do: {:ok, %Result{type: :mobile, os: "sailfish"}, rest}
defp extract_os(<<"(Linux;)", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "linux"}, rest}
defp extract_os(<<"(Linux)", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "linux"}, rest}
defp extract_os(<<"pearOS", rest::binary()>>),
do: {:ok, %Result{type: :desktop, os: "linux"}, rest}
defp extract_os(<<"CFNetwork", _::binary()>>),
do: {:done, %Result{type: :mobile, os: "ios", browser: "webview"}}
defp extract_os(<<"Konqueror", _::binary()>>),
do: {:done, %Result{browser: "konqueror", type: :desktop, os: "unknown"}}
defp extract_os(<<"Lynx", _::binary()>>),
do: {:done, %Result{browser: "lynx", type: :desktop, os: "unknown"}}
defp extract_os(<<"jp.co.yahoo.android.ybrowser", rest::binary()>>),
do: {:ok, %Result{os: "android", browser: "yahoo", type: :mobile}, rest}
# defp extract_os(<<"Kindle Fire", rest::binary()>>),
# do: {:ok, %Result{os: "kindle fire", type: :tablet}, rest}
defp extract_os(<<"; KFT", rest::binary()>>),
do: {:ok, %Result{os: "kindle fire", type: :tablet}, rest}
defp extract_os(<<"; KFOT", rest::binary()>>),
do: {:ok, %Result{os: "kindle fire", type: :tablet}, rest}
defp extract_os(<<"; KFJWI", rest::binary()>>),
do: {:ok, %Result{os: "kindle fire", type: :tablet}, rest}
defp extract_os(<<"Nintendo Switch", _::binary()>>),
do: {:done, %Result{os: "nintendo switch", browser: "nintendo", type: :other}}
defp extract_os(<<"Nokia", _::binary()>>),
do: {:done, %Result{os: "nokia", browser: "nokia", type: :mobile}}
defp extract_os(<<"TV Fire", _::binary()>>), do: smart_tv("fire tv")
# bots
defp extract_os(<<"Googlebot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Applebot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"AdsBot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Bingbot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Slurp", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"DuckDuckBot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Baiduspider", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"YandexBot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"YandexAccessibilityBot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Crawler", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Sogou", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Yahoo Ad monitoring", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"AhrefsBot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"APIs-Google", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Mediapartners-Google", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"FeedFetcher-Google", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"google-speakr", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Google Favicon", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Storebot-Google", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Google Wireless Transcoder", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"MicroAdBot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"PetalBot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"PhantomJS", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"AddSearchBot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"TweetmemeBot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"AccompanyBot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"NinjBot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Botify", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Squidbot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"MAZBot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"iSec_Bot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"GoogleAdwordsExpress", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"facebookexternalhit", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Pinterestbot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"screaming frog", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"; Yeti/", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"Cincraw", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"W3C_CSS_Validator", _::binary()>>), do: {:error, :bot}
defp extract_os(<<"KantarSifoMediaAuditBot", _::binary()>>), do: {:error, :bot}
defp extract_os(<<" Spider/", _::binary()>>), do: {:error, :bot}
defp extract_os(<<_::binary-size(1), rest::binary()>>), do: extract_os(rest)
defp extract_os(""), do: {:error, :unknown}
defp extract_browser(acc, <<"; wv)", rest::binary()>>),
do: {:ok, %{acc | browser: "webview"}, rest}
defp extract_browser(acc, <<"WKWebView", rest::binary()>>),
do: {:ok, %{acc | browser: "webview"}, rest}
defp extract_browser(acc, <<"FxiOS", rest::binary()>>),
do: {:ok, %{acc | browser: "firefox"}, rest}
defp extract_browser(acc, <<"SamsungBrowser", rest::binary()>>),
do: {:ok, %{acc | browser: "samsung browser"}, rest}
defp extract_browser(_, <<"Android TV", _::binary()>>), do: smart_tv("android tv")
defp extract_browser(_, <<"ZIDOO", _::binary()>>), do: smart_tv("android tv")
defp extract_browser(_, <<"NEXBOX", _::binary()>>), do: smart_tv("android tv")
defp extract_browser(_, <<"XBOX", _::binary()>>), do: smart_tv("xbox")
defp extract_browser(_, <<"; AFT", _::binary()>>), do: smart_tv("fire tv")
defp extract_browser(_, <<"BRAVIA", _::binary()>>), do: smart_tv("sony bravia")
defp extract_browser(acc, <<"UCBrowser", rest::binary()>>),
do: {:ok, %{acc | browser: "uc browser"}, rest}
defp extract_browser(_, <<"Chromebook", rest::binary()>>),
do: extract_browser(%Result{os: "chrome os", browser: "chrome", type: :desktop}, rest)
defp extract_browser(acc, <<"Chrome", rest::binary()>>),
do: {:ok, %{acc | browser: "chrome"}, rest}
defp extract_browser(%{os: "ios"} = acc, <<"Safari", rest::binary()>>),
do: {:ok, %{acc | browser: "safari"}, rest}
defp extract_browser(%{os: "mac os"} = acc, <<"Safari", rest::binary()>>),
do: {:ok, %{acc | browser: "safari"}, rest}
defp extract_browser(acc, <<"CriOS", rest::binary()>>),
do: {:ok, %{acc | browser: "chrome"}, rest}
defp extract_browser(acc, <<"Gecko", rest::binary()>>),
do: {:ok, %{acc | browser: "firefox"}, rest}
defp extract_browser(acc, <<"Focus", rest::binary()>>),
do: {:ok, %{acc | browser: "firefox"}, rest}
defp extract_browser(acc, <<"; SM-T", rest::binary()>>),
do: extract_browser(%{acc | type: :tablet}, rest)
defp extract_browser(acc, <<"SAMSUNG-SM-T", rest::binary()>>),
do: extract_browser(%{acc | type: :tablet}, rest)
defp extract_browser(acc, <<"SAMSUNG SM-T", rest::binary()>>),
do: extract_browser(%{acc | type: :tablet}, rest)
defp extract_browser(acc, <<"; SM-P", rest::binary()>>),
do: extract_browser(%{acc | type: :tablet}, rest)
defp extract_browser(acc, <<"Trident", rest::binary()>>),
do: {:ok, %{acc | browser: "ie"}, rest}
defp extract_browser(acc, <<"Opera", rest::binary()>>),
do: {:ok, %{acc | browser: "opera"}, rest}
defp extract_browser(acc, <<"Presto", rest::binary()>>),
do: {:ok, %{acc | browser: "opera"}, rest}
defp extract_browser(acc, <<"OPT/", rest::binary()>>),
do: {:ok, %{acc | browser: "opera"}, rest}
defp extract_browser(acc, <<"OPiOS", rest::binary()>>),
do: {:ok, %{acc | browser: "opera"}, rest}
defp extract_browser(acc, <<"DuckDuckGo/", rest::binary()>>),
do: {:ok, %{acc | browser: "duckduckgo"}, rest}
defp extract_browser(acc, <<"TizenBrowser", rest::binary()>>),
do: {:ok, %{acc | browser: "tizen browser"}, rest}
defp extract_browser(_, <<"FamilyHub", _::binary()>>),
do: {:done, %Result{type: :other, browser: "samsung family hub", os: "tizen"}}
defp extract_browser(acc, <<"coc_coc_browser", rest::binary()>>),
do: {:ok, %{acc | browser: "coc coc"}, rest}
defp extract_browser(acc, <<"like Android", rest::binary()>>),
do: {:ok, %{acc | browser: "android browser"}, rest}
defp extract_browser(%{os: "linux"}, <<" WPE", _::binary()>>),
do: smart_tv("generic set top box")
defp extract_browser(%{os: "android"}, <<"CTV STB", _::binary()>>), do: smart_tv("android tv")
defp extract_browser(acc, <<"BlackBerry", rest::binary()>>),
do: {:ok, %{acc | browser: "blackberry"}, rest}
defp extract_browser(_, <<"BBB", _::binary()>>),
do: {:done, %Result{os: "blackberry", browser: "blackberry", type: :mobile}}
defp extract_browser(acc, <<"Flipboard", rest::binary()>>),
do: {:ok, %{acc | browser: "flipboard"}, rest}
defp extract_browser(acc, <<"freeridegamesPlayer", rest::binary()>>),
do: {:ok, %{acc | browser: "freeridegamesplayer"}, rest}
defp extract_browser(acc, <<"QwantiOS", rest::binary()>>),
do: {:ok, %{acc | browser: "qwant"}, rest}
defp extract_browser(acc, <<"; Tablet; rv", rest::binary()>>),
do: extract_browser(%{acc | type: :tablet}, rest)
defp extract_browser(_, <<"Kindle Fire", rest::binary()>>),
do: extract_browser(%Result{os: "kindle fire", browser: "webview", type: :tablet}, rest)
defp extract_browser(%{os: "kindle fire"} = acc, <<"Silk", rest::binary()>>),
do: {:ok, %{acc | browser: "silk"}, rest}
defp extract_browser(acc, <<"; KFM", rest::binary()>>),
do: extract_browser(%{acc | os: "kindle fire", type: :tablet}, rest)
defp extract_browser(%{os: "tizen"}, <<" TV ", _::binary()>>), do: smart_tv("tizen")
defp extract_browser(_, <<"Xbox", _::binary()>>), do: smart_tv("xbox")
defp extract_browser(_, <<"HeadlessChrome", _::binary()>>), do: {:error, :bot}
defp extract_browser(_, <<"PhantomJS", _::binary()>>), do: {:error, :bot}
defp extract_browser(_, <<"Googlebot", _::binary()>>), do: {:error, :bot}
defp extract_browser(_, <<"Mediapartners-Google", _::binary()>>), do: {:error, :bot}
defp extract_browser(_, <<"DuplexWeb-Google", _::binary()>>), do: {:error, :bot}
defp extract_browser(_, <<"googleweblight", _::binary()>>), do: {:error, :bot}
defp extract_browser(_, <<"Storebot-Google", _::binary()>>), do: {:error, :bot}
defp extract_browser(_, <<"Google-Read-Aloud", _::binary()>>), do: {:error, :bot}
defp extract_browser(_, <<"MicroAdBot", _::binary()>>), do: {:error, :bot}
defp extract_browser(_, <<" Bot ", _::binary()>>), do: {:error, :bot}
defp extract_browser(_, <<" jigsawpuzzle/", _::binary()>>), do: {:error, :bot}
# skip ahead
defp extract_browser(os, <<"like Gecko", rest::binary()>>), do: extract_browser(os, rest)
defp extract_browser(os, <<_::binary-size(1), rest::binary()>>), do: extract_browser(os, rest)
defp extract_browser(%{os: "blackberry"} = acc, ""),
do: {:ok, %{acc | browser: "blackberry"}, ""}
defp extract_browser(acc, ""), do: {:ok, acc, ""}
defp complete(_, <<"Googlebot", _::binary()>>), do: {:error, :bot}
defp complete(_, <<"AdsBot", _::binary()>>), do: {:error, :bot}
defp complete(_, <<"Google-Read-Aloud", _::binary()>>), do: {:error, :bot}
defp complete(_, <<"Google Favicon", _::binary()>>), do: {:error, :bot}
defp complete(_, <<"Applebot", _::binary()>>), do: {:error, :bot}
defp complete(_, <<"MicroAdBot", _::binary()>>), do: {:error, :bot}
defp complete(_, <<"YandexMobileBot", _::binary()>>), do: {:error, :bot}
defp complete(_, <<"TiggeritoBot", _::binary()>>), do: {:error, :bot}
defp complete(_, <<"moatbot", _::binary()>>), do: {:error, :bot}
defp complete(_, <<"jigsawpuzzle", _::binary()>>), do: {:error, :bot}
defp complete(acc, <<"Edge/", rest::binary()>>), do: complete(%{acc | browser: "edge"}, rest)
defp complete(acc, <<"Edg/", rest::binary()>>), do: complete(%{acc | browser: "edge"}, rest)
defp complete(acc, <<"EdgiOS", rest::binary()>>), do: complete(%{acc | browser: "edge"}, rest)
defp complete(acc, <<"EdgA", rest::binary()>>), do: complete(%{acc | browser: "edge"}, rest)
defp complete(acc, <<"KAIOS", rest::binary()>>), do: complete(%{acc | os: "kai os"}, rest)
defp complete(acc, <<"Kai/", rest::binary()>>), do: complete(%{acc | os: "kai os"}, rest)
defp complete(%{os: "linux"}, <<"Philips", _::binary()>>), do: smart_tv("philips")
defp complete(_, <<"GoogleTV/", rest::binary()>>),
do: complete(%Result{os: "googletv", browser: "googletv", type: :smart_tv}, rest)
defp complete(acc, <<"Skyfire/", rest::binary()>>),
do: complete(%{acc | browser: "skyfire"}, rest)
defp complete(acc, <<"Pafari", rest::binary()>>),
do: complete(%{acc | browser: "pafari"}, rest)
defp complete(acc, <<"Microsoft Outlook", rest::binary()>>),
do: complete(%{acc | browser: "outlook"}, rest)
defp complete(acc, <<"YaBrowser", rest::binary()>>),
do: complete(%{acc | browser: "yandex browser"}, rest)
defp complete(acc, <<"QwantMobile", rest::binary()>>),
do: complete(%{acc | browser: "qwant"}, rest)
defp complete(acc, <<"QwantBrowser", rest::binary()>>),
do: complete(%{acc | browser: "qwant"}, rest)
defp complete(acc, <<" OPR/", rest::binary()>>), do: complete(%{acc | browser: "opera"}, rest)
defp complete(acc, <<" Tablet ", rest::binary()>>), do: complete(%{acc | type: :tablet}, rest)
defp complete(_, <<" CrKey", _::binary()>>), do: smart_tv("chromecast")
defp complete(acc, <<_::binary-size(1), rest::binary()>>), do: complete(acc, rest)
defp complete(acc, ""), do: {:ok, acc}
defp smart_tv(os) do
{:done, %Result{type: :smart_tv, os: os, browser: os}}
end
end