Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
.idea/
.vscode/
.DS_Store
old.go
old_test.go
file.txt
user_agents.txt
ua2.go
settings.json
benchmarks.txt
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Miloš Mileusnić
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+99
View File
@@ -0,0 +1,99 @@
# Go/Golang package for parsing user agent strings [![GoDoc](https://godoc.org/github.com/mileusna/useragent?status.svg)](https://godoc.org/github.com/mileusna/useragent)
Use `useragent.Parse(userAgent string)` function to parse browser's and bot's user agents strings and get:
+ User agent name and version (Chrome, Firefox, Googlebot, etc.)
+ Operating system name and version (Windows, Android, iOS etc.)
+ Device type (mobile, desktop, tablet, bot)
+ Device name if available (iPhone, iPad, Huawei VNS-L21)
+ URL provided by the bot (http://www.google.com/bot.html etc.)
## Status
Stable. I use it on high traffic websites on every single request, as well on my [Lite Analytics](https://liteanalytics.com/) service.
I constantly improve user agents detection and performance. Fill free to report an issue for any User-Agent string not recognized or misinterpreted.
## Installation <a id="installation"></a>
```
go get github.com/mileusna/useragent
```
## Example<a id="example"></a>
```go
package main
import (
"fmt"
"strings"
"github.com/mileusna/useragent"
)
func main() {
userAgents := []string{
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.0 Mobile/14F89 Safari/602.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) FxiOS/8.1.1b4948 Mobile/14F89 Safari/603.2.4",
"Mozilla/5.0 (iPad; CPU OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.0 Mobile/14F89 Safari/602.1",
"Mozilla/5.0 (Linux; Android 4.3; GT-I9300 Build/JSS15J) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.125 Mobile Safari/537.36",
"Mozilla/5.0 (Android 4.3; Mobile; rv:54.0) Gecko/54.0 Firefox/54.0",
"Mozilla/5.0 (Linux; Android 4.3; GT-I9300 Build/JSS15J) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36 OPR/42.9.2246.119956",
"Opera/9.80 (Android; Opera Mini/28.0.2254/66.318; U; en) Presto/2.12.423 Version/12.16",
}
for _, s := range userAgents {
ua := useragent.Parse(s)
fmt.Println()
fmt.Println(ua.String)
fmt.Println(strings.Repeat("=", len(ua.String)))
fmt.Println("Name:", ua.Name, "v", ua.Version)
fmt.Println("OS:", ua.OS, "v", ua.OSVersion)
fmt.Println("Device:", ua.Device)
if ua.Mobile {
fmt.Println("(Mobile)")
}
if ua.Tablet {
fmt.Println("(Tablet)")
}
if ua.Desktop {
fmt.Println("(Desktop)")
}
if ua.Bot {
fmt.Println("(Bot)")
}
if ua.URL != "" {
fmt.Println(ua.URL)
}
}
}
```
## Shorthand functions
Beside `UserAgent{}` struct and its properties returned by `useragent.Parse()`, there is a bunch of shorthand functions for most popular browsers and operating systems, so this code:
```go
ua := useragent.Parse(userAgentString)
if ua.OS == "Android" && ua.Name == "Chrome" {
// do something
}
```
can be also written on this way:
```go
ua := useragent.Parse(userAgentString)
if ua.IsAndroid() && ua.IsChrome() {
// do something
}
```
## Notice
+ Opera and Opera Mini are two browsers, since they operate on very different ways.
+ If Googlebot (or any other bot) is detected and it is using its mobile crawler, both `bot` and `mobile` flags will be set to `true`.
+102
View File
@@ -0,0 +1,102 @@
package useragent
// IsWindows shorthand function to check if OS == Windows
func (ua UserAgent) IsWindows() bool {
return ua.OS == Windows
}
// IsAndroid shorthand function to check if OS == Android
func (ua UserAgent) IsAndroid() bool {
return ua.OS == Android
}
// IsMacOS shorthand function to check if OS == MacOS
func (ua UserAgent) IsMacOS() bool {
return ua.OS == MacOS
}
// IsIOS shorthand function to check if OS == IOS
func (ua UserAgent) IsIOS() bool {
return ua.OS == IOS
}
// IsLinux shorthand function to check if OS == Linux
func (ua UserAgent) IsLinux() bool {
return ua.OS == Linux
}
// IsChromeOS shorthand function to check if OS == CrOS
func (ua UserAgent) IsChromeOS() bool {
return ua.OS == ChromeOS || ua.OS == "CrOS"
}
// IsBlackberryOS shorthand function to check if OS == BlackBerry
func (ua UserAgent) IsBlackberryOS() bool {
return ua.OS == BlackBerry
}
// IsOpera shorthand function to check if Name == Opera
func (ua UserAgent) IsOpera() bool {
return ua.Name == Opera
}
// IsOperaMini shorthand function to check if Name == Opera Mini
func (ua UserAgent) IsOperaMini() bool {
return ua.Name == OperaMini
}
// IsChrome shorthand function to check if Name == Chrome
func (ua UserAgent) IsChrome() bool {
return ua.Name == Chrome
}
// IsFirefox shorthand function to check if Name == Firefox
func (ua UserAgent) IsFirefox() bool {
return ua.Name == Firefox
}
// IsInternetExplorer shorthand function to check if Name == Internet Explorer
func (ua UserAgent) IsInternetExplorer() bool {
return ua.Name == InternetExplorer
}
// IsSafari shorthand function to check if Name == Safari
func (ua UserAgent) IsSafari() bool {
return ua.Name == Safari
}
// IsEdge shorthand function to check if Name == Edge
func (ua UserAgent) IsEdge() bool {
return ua.Name == Edge
}
// IsBlackBerry shorthand function to check if Name == BlackBerry
func (ua UserAgent) IsBlackBerry() bool {
return ua.Name == BlackBerry
}
// IsGooglebot shorthand function to check if Name == Googlebot
func (ua UserAgent) IsGooglebot() bool {
return ua.Name == Googlebot
}
// IsTwitterbot shorthand function to check if Name == Twitterbot
func (ua UserAgent) IsTwitterbot() bool {
return ua.Name == Twitterbot
}
// IsFacebookbot shorthand function to check if Name == FacebookExternalHit
func (ua UserAgent) IsFacebookbot() bool {
return ua.Name == FacebookExternalHit
}
// IsYandexbot shorthand function to check if Name == YandexBot
func (ua UserAgent) IsYandexbot() bool {
return ua.Name == YandexBot
}
// IsUnknown returns true if the package can't determine the user agent reliably.
// Fields like Name, OS, etc. might still have values.
func (ua UserAgent) IsUnknown() bool {
return !ua.Mobile && !ua.Tablet && !ua.Desktop && !ua.Bot
}
+691
View File
@@ -0,0 +1,691 @@
package useragent
import (
"bytes"
"regexp"
"strings"
)
// UserAgent struct containing all data extracted from parsed user-agent string
type UserAgent struct {
VersionNo VersionNo
OSVersionNo VersionNo
URL string
String string
Name string
Version string
OS string
OSVersion string
Device string
Mobile bool
Tablet bool
Desktop bool
Bot bool
}
// Constants for browsers and operating systems for easier comparison
const (
Windows = "Windows"
WindowsPhone = "Windows Phone"
WindowsNT = "Windows NT"
WindowsPhoneOS = "Windows Phone OS"
Android = "Android"
MacOS = "macOS"
IOS = "iOS"
Linux = "Linux"
FreeBSD = "FreeBSD"
ChromeOS = "ChromeOS"
BlackBerry = "BlackBerry"
CrOS = "CrOS"
Harmony = "Harmony"
Opera = "Opera"
OperaMini = "Opera Mini"
OperaTouch = "Opera Touch"
Chrome = "Chrome"
HeadlessChrome = "Headless Chrome"
Firefox = "Firefox"
InternetExplorer = "Internet Explorer"
Safari = "Safari"
Edge = "Edge"
Vivaldi = "Vivaldi"
MobileSafari = "Mobile Safari"
NetFront = "NetFront"
Mozilla = "Mozilla"
Msie = "MSIE"
SamsungBrowser = "Samsung Browser"
GoogleAdsBot = "Google Ads Bot"
Googlebot = "Googlebot"
Twitterbot = "Twitterbot"
FacebookExternalHit = "facebookexternalhit"
Applebot = "Applebot"
Bingbot = "Bingbot"
YandexBot = "YandexBot"
YandexAdNet = "YandexAdNet"
FacebookApp = "Facebook App"
InstagramApp = "Instagram App"
TiktokApp = "TikTok App"
Version = "Version"
Mobile = "Mobile"
Tablet = "Tablet"
tablet = "tablet"
)
// Parse user agent string returning UserAgent struct
func Parse(userAgent string) UserAgent {
ua := UserAgent{
String: userAgent,
}
tokens := parse([]byte(userAgent))
ua.URL = tokens.url
// OS lookup
switch {
case tokens.exists(Android):
ua.OS = Android
var osIndex int
osIndex, ua.OSVersion = tokens.getIndexValue(Android)
ua.Tablet = strings.Contains(strings.ToLower(ua.String), tablet)
ua.Device = tokens.findAndroidDevice(osIndex)
case tokens.exists("iPhone"):
ua.OS = IOS
ua.OSVersion = tokens.findMacOSVersion()
ua.Device = "iPhone"
ua.Mobile = true
case tokens.exists("iPad"):
ua.OS = IOS
ua.OSVersion = tokens.findMacOSVersion()
ua.Device = "iPad"
ua.Tablet = true
case tokens.exists(WindowsNT):
ua.OS = Windows
ua.OSVersion = tokens.get(WindowsNT)
ua.Desktop = true
case tokens.exists(WindowsPhoneOS):
ua.OS = WindowsPhone
ua.OSVersion = tokens.get(WindowsPhoneOS)
ua.Mobile = true
case tokens.exists("Macintosh"):
ua.OS = MacOS
ua.OSVersion = tokens.findMacOSVersion()
ua.Desktop = true
case tokens.exists(Linux):
ua.OS = Linux
ua.OSVersion = tokens.get(Linux)
ua.Desktop = true
case tokens.exists(FreeBSD):
ua.OS = FreeBSD
ua.OSVersion = tokens.get(FreeBSD)
ua.Desktop = true
case tokens.exists(CrOS):
ua.OS = ChromeOS
ua.OSVersion = tokens.get(CrOS)
ua.Desktop = true
case tokens.exists(BlackBerry):
ua.OS = BlackBerry
ua.OSVersion = tokens.get(BlackBerry)
ua.Mobile = true
case tokens.exists("OpenHarmony"):
ua.OS = Harmony
ua.OSVersion = tokens.get("OpenHarmony")
ua.Mobile = true
}
switch {
case tokens.exists(Googlebot):
ua.Name = Googlebot
ua.Version = tokens.get(Googlebot)
ua.Bot = true
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
case tokens.existsAny("GoogleProber", "GoogleProducer"):
if name := tokens.findBestMatch(false); name != "" {
ua.Name = name
}
ua.Bot = true
case tokens.exists("Bytespider"):
ua.Name = "Bytespider"
ua.Mobile = tokens.exists("Mobile Safari")
ua.Bot = true
case tokens.exists(Applebot):
ua.Name = Applebot
ua.Version = tokens.get(Applebot)
ua.Bot = true
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
ua.OS = ""
case tokens.get(OperaMini) != "":
ua.Name = OperaMini
ua.Version = tokens.get(OperaMini)
ua.Mobile = true
case tokens.get("OPR") != "":
ua.Name = Opera
ua.Version = tokens.get("OPR")
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
case tokens.get("OPT") != "":
ua.Name = OperaTouch
ua.Version = tokens.get("OPT")
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
// Opera on iOS
case tokens.get("OPiOS") != "":
ua.Name = Opera
ua.Version = tokens.get("OPiOS")
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
// Chrome on iOS
case tokens.get("CriOS") != "":
ua.Name = Chrome
ua.Version = tokens.get("CriOS")
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
// Firefox on iOS
case tokens.get("FxiOS") != "":
ua.Name = Firefox
ua.Version = tokens.get("FxiOS")
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
case tokens.get(Firefox) != "":
ua.Name = Firefox
ua.Version = tokens.get(Firefox)
ua.Mobile = tokens.exists(Mobile)
ua.Tablet = tokens.exists(Tablet)
case tokens.get(Vivaldi) != "":
ua.Name = Vivaldi
ua.Version = tokens.get(Vivaldi)
case tokens.exists(Msie):
ua.Name = InternetExplorer
ua.Version = tokens.get(Msie)
case tokens.get("EdgiOS") != "":
ua.Name = Edge
ua.Version = tokens.get("EdgiOS")
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
case tokens.get(Edge) != "":
ua.Name = Edge
ua.Version = tokens.get(Edge)
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
case tokens.get("Edg") != "":
ua.Name = Edge
ua.Version = tokens.get("Edg")
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
case tokens.get("EdgA") != "":
ua.Name = Edge
ua.Version = tokens.get("EdgA")
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
case tokens.get("bingbot") != "":
ua.Name = Bingbot
ua.Version = tokens.get("bingbot")
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
case tokens.get(YandexBot) != "":
ua.Name = YandexBot
ua.Version = tokens.get(YandexBot)
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
ua.Bot = true
case tokens.get(YandexAdNet) != "":
ua.Name = YandexAdNet
ua.Version = tokens.get(YandexAdNet)
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
ua.Bot = true
case tokens.get("SamsungBrowser") != "":
ua.Name = SamsungBrowser
ua.Version = tokens.get("SamsungBrowser")
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
ua.OS = Android
case tokens.get("HeadlessChrome") != "":
ua.Name = HeadlessChrome
ua.Version = tokens.get("HeadlessChrome")
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
ua.Bot = true
case tokens.existsAny("AdsBot-Google-Mobile", "Mediapartners-Google", "AdsBot-Google"):
ua.Name = GoogleAdsBot
ua.Bot = true
ua.Mobile = ua.IsAndroid() || ua.IsIOS()
case tokens.exists("Yahoo Ad monitoring"):
ua.Name = "Yahoo Ad monitoring"
ua.Bot = true
ua.Mobile = ua.IsAndroid() || ua.IsIOS()
case tokens.exists("XiaoMi"):
miui := tokens.get("XiaoMi")
if strings.HasPrefix(miui, "MiuiBrowser") {
ua.Name = "Miui Browser"
ua.Version = strings.TrimPrefix(miui, "MiuiBrowser/")
ua.Mobile = true
}
case tokens.exists("FBAN"):
ua.Name = FacebookApp
ua.Version = tokens.get("FBAN")
case tokens.exists("FB_IAB"):
ua.Name = FacebookApp
ua.Version = tokens.get("FBAV")
case tokens.startsWith("Instagram"):
ua.Name = InstagramApp
ua.Version = tokens.findInstagramVersion()
case tokens.exists("BytedanceWebview"):
ua.Name = TiktokApp
ua.Version = tokens.get("app_version")
case tokens.get("HuaweiBrowser") != "":
ua.Name = "Huawei Browser"
ua.Version = tokens.get("HuaweiBrowser")
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
case tokens.exists(BlackBerry):
ua.Name = BlackBerry
ua.Version = tokens.get(Version)
case tokens.exists(NetFront):
ua.Name = NetFront
ua.Version = tokens.get(NetFront)
ua.Mobile = true
// if Chrome and Safari defined, find any other token sent descr
case tokens.exists(Chrome) && tokens.exists(Safari):
name := tokens.findBestMatch(true)
if name != "" {
ua.Name = name
ua.Version = tokens.get(name)
break
}
fallthrough
case tokens.exists(Chrome):
ua.Name = Chrome
ua.Version = tokens.get(Chrome)
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
case tokens.exists("Brave Chrome"):
ua.Name = Chrome
ua.Version = tokens.get("Brave Chrome")
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
case tokens.exists(Safari):
ua.Name = Safari
v := tokens.get(Version)
if v != "" {
ua.Version = v
} else {
ua.Version = tokens.get(Safari)
}
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
default:
if ua.IsAndroid() && tokens.get(Version) != "" {
ua.Name = "Android browser"
ua.Version = tokens.get(Version)
ua.Mobile = true
} else {
if name := tokens.findBestMatch(false); name != "" {
ua.Name = name
ua.Version = tokens.get(name)
} else {
ua.Name = ua.String
}
ua.Bot = strings.Contains(strings.ToLower(ua.Name), "bot")
// If mobile flag has already been set, don't override it.
if !ua.Mobile {
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
}
}
}
if ua.IsAndroid() {
ua.Mobile = true
}
// if tablet, switch mobile to off
if ua.Tablet {
ua.Mobile = false
}
// if not already bot, check some popular bots and whether URL is set
if !ua.Bot {
switch ua.Name {
case Twitterbot, FacebookExternalHit, "facebookcatalog":
ua.Bot = true
default:
ua.Bot = ua.URL != ""
}
}
ua.VersionNo = parseVersion(ua.Version)
ua.OSVersionNo = parseVersion(ua.OSVersion)
return ua
}
// var buffPool = sync.Pool{New: func() interface{} {
// return bytes.NewBuffer(make([]byte, 0, 30))
// }}
func parse(userAgent []byte) properties {
clients := properties{
list: make([]property, 0, 8),
}
slash := false
isURL := false
// buff := buffPool.Get().(*bytes.Buffer)
// val := buffPool.Get().(*bytes.Buffer)
// buff.Reset()
// val.Reset()
buff := bytes.NewBuffer(make([]byte, 0, 30))
val := bytes.NewBuffer(make([]byte, 0, 30))
addToken := func() {
if buff.Len() != 0 {
s := string(bytes.TrimSpace(buff.Bytes()))
if !ignore(s) {
if isURL {
clients.url = strings.TrimPrefix(s, "+")
return
}
if val.Len() == 0 {
// if value don't exists, try to get version from the token
clients.list = append(clients.list, checkVer(s))
} else {
clients.list = append(clients.list, property{Key: s, Value: string(bytes.TrimSpace(val.Bytes()))})
}
}
}
buff.Reset()
val.Reset()
slash = false
isURL = false
}
parOpen := false
braOpen := false
for i, c := range userAgent {
switch {
case c == 41: // )
addToken()
parOpen = false
case (parOpen || braOpen) && c == 59: // ;
addToken()
case c == 59: // ;
addToken()
case c == 40: // (
addToken()
parOpen = true
case c == 91: // [
addToken()
braOpen = true
case c == 93: // ]
addToken()
braOpen = false
case c == 58: // :
if bytes.HasSuffix(buff.Bytes(), []byte("http")) || bytes.HasSuffix(buff.Bytes(), []byte("https")) {
// If we are part of a URL just write the character.
buff.WriteByte(c)
} else if i != len(userAgent)-1 && userAgent[i+1] != ' ' {
// If the following character is not a space, change to a space.
buff.WriteByte(' ')
}
// Otherwise don't write as it's probably a badly formatted key value separator.
case slash && c == 32:
addToken()
case slash:
val.WriteByte(c)
case c == 47 && !isURL: // /
if i != len(userAgent)-1 && userAgent[i+1] == 47 && (bytes.HasSuffix(buff.Bytes(), []byte("http:")) || bytes.HasSuffix(buff.Bytes(), []byte("https:"))) {
buff.WriteByte(c)
isURL = true
} else {
if ignore(buff.String()) {
buff.Reset()
} else {
slash = true
}
}
default:
buff.WriteByte(c)
}
}
addToken()
// buffPool.Put(buff)
// buffPool.Put(val)
return clients
}
func checkVer(s string) property {
i := strings.LastIndex(s, " ")
if i == -1 {
return property{Key: s, Value: ""}
}
switch s[:i] {
case Linux, WindowsNT, WindowsPhoneOS, Msie, Android, "OpenHarmony":
return property{Key: s[:i], Value: s[i+1:]}
case "CrOS x86_64", "CrOS aarch64", "CrOS armv7l":
j := strings.LastIndex(s[:i], " ")
return property{Key: s[:j], Value: s[j+1 : i]}
default:
return property{Key: s, Value: ""}
}
}
// ignore returns true if token should be ignored
func ignore(s string) bool {
switch s {
case "KHTML, like Gecko", "U", "compatible", Mozilla, "WOW64", "en", "en-us", "en-gb", "ru-ru", "Browser":
return true
default:
return false
}
}
type property struct {
Key string
Value string
}
type properties struct {
list []property
url string
}
func (p properties) get(key string) string {
for _, prop := range p.list {
if prop.Key == key {
return prop.Value
}
}
return ""
}
func (p properties) getIndexValue(key string) (int, string) {
for i, prop := range p.list {
if prop.Key == key {
return i, prop.Value
}
}
return -1, ""
}
func (p properties) exists(key string) bool {
for _, prop := range p.list {
if prop.Key == key {
return true
}
}
return false
}
// func (p properties) existsIgnoreCase(key string) bool {
// for _, prop := range p.list {
// if strings.EqualFold(prop.Key, key) {
// return true
// }
// }
// return false
// }
func (p properties) existsAny(keys ...string) bool {
for _, k := range keys {
for _, prop := range p.list {
if prop.Key == k {
return true
}
}
}
return false
}
func (p properties) getAny(keys ...string) (key, value string) {
for _, k := range keys {
for _, prop := range p.list {
if prop.Key == k {
return prop.Key, prop.Value
}
}
}
return "", ""
}
func (p properties) findMacOSVersion() string {
for _, token := range p.list {
if strings.Contains(token.Key, "OS") {
if ver := findVersion(token.Value); ver != "" {
return ver
} else if ver = findVersion(token.Key); ver != "" {
return ver
}
}
}
return ""
}
func (p properties) startsWith(value string) bool {
for _, prop := range p.list {
if strings.HasPrefix(prop.Key, value) {
return true
}
}
return false
}
func (p properties) findInstagramVersion() string {
for _, token := range p.list {
if strings.HasPrefix(token.Key, "Instagram") {
if ver := findVersion(token.Value); ver != "" {
return ver
} else if ver = findVersion(token.Key); ver != "" {
return ver
}
}
}
return ""
}
// findBestMatch from the rest of the bunch
// in first cycle only return key with version value
// if withVerValue is false, do another cycle and return any token
func (p properties) findBestMatch(withVerOnly bool) string {
n := 2
if withVerOnly {
n = 1
}
for i := 0; i < n; i++ {
for _, prop := range p.list {
switch prop.Key {
case Chrome, Firefox, Safari, Version, Mobile, MobileSafari, Mozilla, "AppleWebKit", WindowsNT, WindowsPhoneOS, Android, "Macintosh", Linux, "GSA", CrOS, Tablet, "OpenHarmony":
default:
// don't pick if starts with number
if len(prop.Key) != 0 && prop.Key[0] >= 48 && prop.Key[0] <= 57 {
break
}
if i == 0 {
if prop.Value != "" { // in first check, only return keys with value
return prop.Key
}
} else {
return prop.Key
}
}
}
}
return ""
}
var rxMacOSVer = regexp.MustCompile(`[_\d\.]+`)
func findVersion(s string) string {
if ver := rxMacOSVer.FindString(s); ver != "" {
return strings.Replace(ver, "_", ".", -1)
}
return ""
}
// findAndroidDevice in tokens
func (p *properties) findAndroidDevice(startIndex int) string {
for i := startIndex; i < startIndex+1; i++ {
if len(p.list) > i+1 {
dev := p.list[i+1].Key
if len(dev) == 2 || (len(dev) == 5 && dev[2] == '-') {
// probably language tag (en-us etc..), ignore and continue loop
continue
}
switch dev {
case Chrome, Firefox, Safari, OperaMini, "Presto", Version, Mobile, MobileSafari, Mozilla, "AppleWebKit", WindowsNT, WindowsPhoneOS, Android, "Macintosh", Linux, CrOS:
// ignore these tokens, not device names
default:
if strings.Contains(strings.ToLower(dev), tablet) {
p.list[i+1].Key = Tablet // leave Tablet tag for later table detection
} else {
p.list = append(p.list[:i+1], p.list[i+2:]...)
}
return strings.TrimSpace(strings.TrimSuffix(dev, "Build"))
}
}
}
return ""
}
+67
View File
@@ -0,0 +1,67 @@
package useragent
import (
"fmt"
"strconv"
"strings"
)
type VersionNo struct {
Major int
Minor int
Patch int
}
// parseVersion parse version string into Major.Minor.Patch struct
func parseVersion(ver string) (verno VersionNo) {
var err error
parts := strings.Split(ver, ".")
if len(parts) > 0 {
if verno.Major, err = strconv.Atoi(parts[0]); err != nil {
return verno
}
if len(parts) > 1 {
if verno.Minor, err = strconv.Atoi(parts[1]); err != nil {
return verno
}
if len(parts) > 2 {
if verno.Patch, err = strconv.Atoi(parts[2]); err != nil {
return verno
}
}
}
}
return verno
}
// VersionNoShort return version string in format <Major>.<Minor>
func (ua UserAgent) VersionNoShort() string {
if ua.VersionNo.Major == 0 && ua.VersionNo.Minor == 0 && ua.VersionNo.Patch == 0 {
return ""
}
return fmt.Sprintf("%d.%d", ua.VersionNo.Major, ua.VersionNo.Minor)
}
// VersionNoFull returns version string in format <Major>.<Minor>.<Patch>
func (ua UserAgent) VersionNoFull() string {
if ua.VersionNo.Major == 0 && ua.VersionNo.Minor == 0 && ua.VersionNo.Patch == 0 {
return ""
}
return fmt.Sprintf("%d.%d.%d", ua.VersionNo.Major, ua.VersionNo.Minor, ua.VersionNo.Patch)
}
// OSVersionNoShort returns OS version string in format <Major>.<Minor>
func (ua UserAgent) OSVersionNoShort() string {
if ua.OSVersionNo.Major == 0 && ua.OSVersionNo.Minor == 0 && ua.OSVersionNo.Patch == 0 {
return ""
}
return fmt.Sprintf("%d.%d", ua.OSVersionNo.Major, ua.OSVersionNo.Minor)
}
// OSVersionNoFull returns OS version string in format <Major>.<Minor>.<Patch>
func (ua UserAgent) OSVersionNoFull() string {
if ua.OSVersionNo.Major == 0 && ua.OSVersionNo.Minor == 0 && ua.OSVersionNo.Patch == 0 {
return ""
}
return fmt.Sprintf("%d.%d.%d", ua.OSVersionNo.Major, ua.OSVersionNo.Minor, ua.OSVersionNo.Patch)
}