2024-08-13 16:31:28 +02:00
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"sort"
"sync"
"time"
)
type BrowserVersion struct {
Version string ` json:"version" `
Global float64 ` json:"global" `
}
type BrowserData struct {
Firefox [ ] BrowserVersion ` json:"firefox" `
Chromium [ ] BrowserVersion ` json:"chrome" `
}
var (
cache = struct {
sync . RWMutex
data map [ string ] string
} {
data : make ( map [ string ] string ) ,
}
browserCache = struct {
sync . RWMutex
data BrowserData
expires time . Time
} {
expires : time . Now ( ) ,
}
)
func fetchLatestBrowserVersions ( ) ( BrowserData , error ) {
url := "https://raw.githubusercontent.com/Fyrd/caniuse/master/fulldata-json/data-2.0.json"
resp , err := http . Get ( url )
if err != nil {
return BrowserData { } , err
}
defer resp . Body . Close ( )
body , err := ioutil . ReadAll ( resp . Body )
if err != nil {
return BrowserData { } , err
}
var rawData map [ string ] interface { }
if err := json . Unmarshal ( body , & rawData ) ; err != nil {
return BrowserData { } , err
}
stats := rawData [ "agents" ] . ( map [ string ] interface { } )
var data BrowserData
if firefoxData , ok := stats [ "firefox" ] . ( map [ string ] interface { } ) ; ok {
for version , usage := range firefoxData [ "usage_global" ] . ( map [ string ] interface { } ) {
data . Firefox = append ( data . Firefox , BrowserVersion {
Version : version ,
Global : usage . ( float64 ) ,
} )
}
}
if chromeData , ok := stats [ "chrome" ] . ( map [ string ] interface { } ) ; ok {
for version , usage := range chromeData [ "usage_global" ] . ( map [ string ] interface { } ) {
data . Chromium = append ( data . Chromium , BrowserVersion {
Version : version ,
Global : usage . ( float64 ) ,
} )
}
}
return data , nil
}
func getLatestBrowserVersions ( ) ( BrowserData , error ) {
browserCache . RLock ( )
if time . Now ( ) . Before ( browserCache . expires ) {
data := browserCache . data
browserCache . RUnlock ( )
return data , nil
}
browserCache . RUnlock ( )
data , err := fetchLatestBrowserVersions ( )
if err != nil {
return BrowserData { } , err
}
browserCache . Lock ( )
browserCache . data = data
browserCache . expires = time . Now ( ) . Add ( 24 * time . Hour )
browserCache . Unlock ( )
return data , nil
}
func randomUserAgent ( ) ( string , error ) {
browsers , err := getLatestBrowserVersions ( )
if err != nil {
return "" , err
}
rand . Seed ( time . Now ( ) . UnixNano ( ) )
// Simulated browser usage statistics (in percentages)
usageStats := map [ string ] float64 {
"Firefox" : 30.0 ,
"Chromium" : 70.0 ,
}
// Calculate the probabilities for the versions
probabilities := [ ] float64 { 0.5 , 0.25 , 0.125 , 0.0625 , 0.03125 , 0.015625 , 0.0078125 , 0.00390625 }
// Select a browser based on usage statistics
browserType := ""
randVal := rand . Float64 ( ) * 100
cumulative := 0.0
for browser , usage := range usageStats {
cumulative += usage
if randVal < cumulative {
browserType = browser
break
}
}
var versions [ ] BrowserVersion
switch browserType {
case "Firefox" :
versions = browsers . Firefox
case "Chromium" :
versions = browsers . Chromium
}
if len ( versions ) == 0 {
return "" , fmt . Errorf ( "no versions found for browser: %s" , browserType )
}
// Sort versions by usage (descending order)
sort . Slice ( versions , func ( i , j int ) bool {
return versions [ i ] . Global > versions [ j ] . Global
} )
// Select a version based on the probabilities
version := ""
randVal = rand . Float64 ( )
cumulative = 0.0
for i , p := range probabilities {
cumulative += p
if randVal < cumulative && i < len ( versions ) {
version = versions [ i ] . Version
break
}
}
if version == "" {
version = versions [ len ( versions ) - 1 ] . Version
}
// Generate the user agent string
userAgent := generateUserAgent ( browserType , version )
return userAgent , nil
}
func generateUserAgent ( browser , version string ) string {
oses := [ ] struct {
os string
probability float64
} {
{ "Windows NT 10.0; Win64; x64" , 44.0 } ,
{ "Windows NT 11.0; Win64; x64" , 44.0 } ,
{ "X11; Linux x86_64" , 1.0 } ,
{ "X11; Ubuntu; Linux x86_64" , 1.0 } ,
{ "Macintosh; Intel Mac OS X 10_15_7" , 10.0 } ,
}
// Select an OS based on probabilities
randVal := rand . Float64 ( ) * 100
cumulative := 0.0
selectedOS := ""
for _ , os := range oses {
cumulative += os . probability
if randVal < cumulative {
selectedOS = os . os
break
}
}
switch browser {
case "Firefox" :
return fmt . Sprintf ( "Mozilla/5.0 (%s; rv:%s) Gecko/20100101 Firefox/%s" , selectedOS , version , version )
case "Chromium" :
return fmt . Sprintf ( "Mozilla/5.0 (%s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36" , selectedOS , version )
}
return ""
}
func updateCachedUserAgents ( newVersions BrowserData ) {
cache . Lock ( )
defer cache . Unlock ( )
for key , userAgent := range cache . data {
randVal := rand . Float64 ( )
if randVal < 0.5 {
updatedUserAgent := updateUserAgentVersion ( userAgent , newVersions )
cache . data [ key ] = updatedUserAgent
}
}
}
func updateUserAgentVersion ( userAgent string , newVersions BrowserData ) string {
// Parse the current user agent to extract browser and version
var browserType , version string
if _ , err := fmt . Sscanf ( userAgent , "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36" , & version ) ; err == nil {
browserType = "Chromium"
} else if _ , err := fmt . Sscanf ( userAgent , "Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36" , & version ) ; err == nil {
browserType = "Chromium"
} else if _ , err := fmt . Sscanf ( userAgent , "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36" , & version ) ; err == nil {
browserType = "Chromium"
} else if _ , err := fmt . Sscanf ( userAgent , "Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36" , & version ) ; err == nil {
browserType = "Chromium"
} else if _ , err := fmt . Sscanf ( userAgent , "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36" , & version ) ; err == nil {
browserType = "Chromium"
} else if _ , err := fmt . Sscanf ( userAgent , "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:%s) Gecko/20100101 Firefox/%s" , & version , & version ) ; err == nil {
browserType = "Firefox"
} else if _ , err := fmt . Sscanf ( userAgent , "Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:%s) Gecko/20100101 Firefox/%s" , & version , & version ) ; err == nil {
browserType = "Firefox"
} else if _ , err := fmt . Sscanf ( userAgent , "Mozilla/5.0 (X11; Linux x86_64; rv:%s) Gecko/20100101 Firefox/%s" , & version , & version ) ; err == nil {
browserType = "Firefox"
} else if _ , err := fmt . Sscanf ( userAgent , "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:%s) Gecko/20100101 Firefox/%s" , & version , & version ) ; err == nil {
browserType = "Firefox"
} else if _ , err := fmt . Sscanf ( userAgent , "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; rv:%s) Gecko/20100101 Firefox/%s" , & version , & version ) ; err == nil {
browserType = "Firefox"
}
// Get the latest version for the browser type
var latestVersion string
if browserType == "Firefox" {
latestVersion = newVersions . Firefox [ 0 ] . Version
} else if browserType == "Chromium" {
latestVersion = newVersions . Chromium [ 0 ] . Version
}
// Update the user agent string with the new version
return generateUserAgent ( browserType , latestVersion )
}
func periodicUpdate ( ) {
for {
// Sleep for a random interval between 1 and 2 days
time . Sleep ( time . Duration ( 24 + rand . Intn ( 24 ) ) * time . Hour )
// Fetch the latest browser versions
newVersions , err := fetchLatestBrowserVersions ( )
if err != nil {
printWarn ( "Error fetching latest browser versions: %v" , err )
continue
}
// Update the browser version cache
browserCache . Lock ( )
browserCache . data = newVersions
browserCache . expires = time . Now ( ) . Add ( 24 * time . Hour )
browserCache . Unlock ( )
// Update the cached user agents
updateCachedUserAgents ( newVersions )
}
}
func GetUserAgent ( cacheKey string ) ( string , error ) {
cache . RLock ( )
userAgent , found := cache . data [ cacheKey ]
cache . RUnlock ( )
if found {
return userAgent , nil
}
userAgent , err := randomUserAgent ( )
if err != nil {
return "" , err
}
cache . Lock ( )
cache . data [ cacheKey ] = userAgent
cache . Unlock ( )
return userAgent , nil
}
func GetNewUserAgent ( cacheKey string ) ( string , error ) {
userAgent , err := randomUserAgent ( )
if err != nil {
return "" , err
}
cache . Lock ( )
cache . data [ cacheKey ] = userAgent
cache . Unlock ( )
return userAgent , nil
}
func init ( ) {
go periodicUpdate ( )
}
// func main() {
// go periodicUpdate() // not needed here
// cacheKey := "image-search"
// userAgent, err := GetUserAgent(cacheKey)
// if err != nil {
// fmt.Println("Error:", err)
// return
// }
// fmt.Println("Generated User Agent:", userAgent)
// // Request a new user agent for the same key
// newUserAgent, err := GetNewUserAgent(cacheKey)
// if err != nil {
// fmt.Println("Error:", err)
// return
// }
// fmt.Println("New User Agent:", newUserAgent)
// AcacheKey := "image-search"
// AuserAgent, err := GetUserAgent(AcacheKey)
// if err != nil {
// fmt.Println("Error:", err)
// return
// }
// fmt.Println("Generated User Agent:", AuserAgent)
// DcacheKey := "image-search"
// DuserAgent, err := GetUserAgent(DcacheKey)
// if err != nil {
// fmt.Println("Error:", err)
// return
// }
// fmt.Println("Generated User Agent:", DuserAgent)
// }