Current section

Files

Jump to
os_detect lib os_detect.ex
Raw

lib/os_detect.ex

defmodule OsDetect do
@moduledoc """
A fast and User Agent parser which only returns the operating system.
"""
alias OsDetect.Patterns
@doc """
Parse the operating system 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")
"macos"
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")
"android"
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")
"ios"
"""
@spec parse(user_agent :: binary() | nil) :: operating_system :: binary() | nil
def parse(nil), do: nil
def parse(user_agent) do
sanitized = sanitize(user_agent)
case extract(sanitized) do
extracted when is_binary(extracted) -> extracted
nil -> parse_os(sanitized, Patterns.list())
end
end
defp parse_os(user_agent, patterns) do
patterns
|> search(user_agent)
|> downcase()
|> normalize()
end
defp sanitize(user_agent), do: user_agent |> String.trim()
defp search([], _string), do: nil
defp search([%{regex: regex} | groups], string) do
case Regex.run(regex, string) do
[_, match | _] -> match
[match] -> match
nil -> search(groups, string)
end
end
defp normalize(nil), do: nil
defp normalize("mac os x"), do: "macos"
defp normalize("cros"), do: "chrome os"
defp normalize("ubuntu"), do: "linux"
defp normalize("fedora"), do: "linux"
defp normalize("debian"), do: "linux"
defp normalize(other), do: other
defp downcase(str) when is_binary(str), do: String.downcase(str)
defp downcase(other), do: other
defp extract(<<"Android", _rest::binary()>>), do: "android"
defp extract(<<"iPhone", _rest::binary()>>), do: "ios"
defp extract(<<"iPad", _rest::binary()>>), do: "ios"
defp extract(<<"iPod", _rest::binary()>>), do: "ios"
defp extract(<<"Macintosh", _rest::binary()>>), do: "macos"
defp extract(<<"Windows Phone", _rest::binary()>>), do: "windows phone"
defp extract(<<"Windows Mobile", _rest::binary()>>), do: "windows phone"
defp extract(<<"Windows NT", _rest::binary()>>), do: "windows"
defp extract(<<"PlayStation", _rest::binary()>>), do: "playstation"
# bots
defp extract(<<"Googlebot", _rest::binary()>>), do: "bot"
defp extract(<<"AdsBot", _rest::binary()>>), do: "bot"
defp extract(<<"Bingbot", _rest::binary()>>), do: "bot"
defp extract(<<"Slurp", _rest::binary()>>), do: "bot"
defp extract(<<"DuckDuckBot", _rest::binary()>>), do: "bot"
defp extract(<<"Baiduspider", _rest::binary()>>), do: "bot"
defp extract(<<"YandexBot", _rest::binary()>>), do: "bot"
defp extract(<<"Sogou", _rest::binary()>>), do: "bot"
defp extract(<<"Yahoo Ad monitoring", _rest::binary()>>), do: "bot"
defp extract(<<"AhrefsBot", _rest::binary()>>), do: "bot"
defp extract(<<_::binary-size(1), rest::binary()>>), do: extract(rest)
defp extract(""), do: nil
end