2023-06-23 04:08:00 +03:00
package cmd
import (
"fmt"
2025-09-30 05:27:54 +03:00
"strconv"
2023-06-30 22:49:58 +03:00
"strings"
2023-06-23 04:08:00 +03:00
2023-06-23 04:16:45 +03:00
"github.com/karust/openserp/core"
2023-06-23 04:08:00 +03:00
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
const (
2026-03-17 02:18:51 +03:00
version = "0.5.6"
2023-06-30 22:49:58 +03:00
defaultConfigFilename = "config"
envPrefix = "OPENSERP"
2023-06-23 04:08:00 +03:00
)
2023-06-30 22:49:58 +03:00
type Config struct {
2025-09-30 05:27:54 +03:00
App AppConfig ` mapstructure:"app" `
Config2Capcha Config2Captcha ` mapstructure:"2captcha" `
GoogleConfig core . SearchEngineOptions ` mapstructure:"google" `
YandexConfig core . SearchEngineOptions ` mapstructure:"yandex" `
BaiduConfig core . SearchEngineOptions ` mapstructure:"baidu" `
BingConfig core . SearchEngineOptions ` mapstructure:"bing" `
DuckDuckGoConfig core . SearchEngineOptions ` mapstructure:"duckduckgo" `
2024-05-06 01:13:07 +03:00
}
type Config2Captcha struct {
ApiKey string ` mapstructure:"apikey" `
2023-06-30 22:49:58 +03:00
}
2023-06-23 04:08:00 +03:00
type AppConfig struct {
2023-06-30 22:49:58 +03:00
Host string ` mapstructure:"host" `
Port int ` mapstructure:"port" `
Timeout int ` mapstructure:"timeout" `
ConfigPath string ` mapstructure:"config_path" `
2026-03-17 02:18:51 +03:00
BrowserPath string ` mapstructure:"browser_path" `
2023-06-30 22:49:58 +03:00
IsBrowserHead bool ` mapstructure:"head" `
IsLeaveHead bool ` mapstructure:"leave_head" `
IsLeakless bool ` mapstructure:"leakless" `
IsDebug bool ` mapstructure:"debug" `
IsVerbose bool ` mapstructure:"verbose" `
IsRawRequests bool ` mapstructure:"raw_requests" `
2025-04-08 16:06:41 +08:00
ProxyURL string ` mapstructure:"proxy" `
Insecure bool ` mapstructure:"insecure" `
2025-08-18 22:49:45 +03:00
IsStealth bool ` mapstructure:"stealth" `
2023-06-23 04:08:00 +03:00
}
2023-06-30 22:49:58 +03:00
var config = Config { }
2023-06-23 04:08:00 +03:00
2025-09-30 05:27:54 +03:00
var flagToConfigKey = map [ string ] string {
"config" : "app.config_path" ,
2026-03-17 02:18:51 +03:00
"browser-path" : "app.browser_path" ,
2025-09-30 05:27:54 +03:00
"leave" : "app.leave_head" ,
"raw" : "app.raw_requests" ,
"2captcha_key" : "2captcha.apikey" ,
}
2023-06-23 04:08:00 +03:00
var RootCmd = & cobra . Command {
2023-06-30 22:49:58 +03:00
Use : "openserp" ,
Short : "Open SERP" ,
Long : ` Get [Google, Yandex, Baidu] search engine results via API or CLI. ` ,
Version : version ,
SilenceUsage : true ,
2023-06-23 04:08:00 +03:00
PersistentPreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
2023-06-30 22:49:58 +03:00
core . InitLogger ( config . App . IsVerbose , config . App . IsDebug )
2023-06-24 22:26:36 +03:00
err := initializeConfig ( cmd )
2023-06-30 22:49:58 +03:00
if err != nil {
return err
}
logrus . Debugf ( "Final config: %+v" , config )
return nil
2023-06-23 04:08:00 +03:00
} ,
2023-06-30 22:49:58 +03:00
2023-06-23 04:08:00 +03:00
// Run: func(cmd *cobra.Command, args []string) {
// // Working with OutOrStdout/OutOrStderr allows us to unit test our command easier
// //out := cmd.OutOrStdout()
2023-06-30 22:49:58 +03:00
// logrus.Trace("Config:", config)
2023-06-23 04:08:00 +03:00
// },
}
// Bind each cobra flag to its associated viper configuration (config file and environment variable)
func bindFlags ( cmd * cobra . Command , vpr * viper . Viper ) {
cmd . Flags ( ) . VisitAll ( func ( flg * pflag . Flag ) {
2025-09-30 05:27:54 +03:00
configName , ok := flagToConfigKey [ flg . Name ]
if ! ok {
configName = "app." + flg . Name
}
if err := vpr . BindPFlag ( configName , flg ) ; err != nil {
logrus . Errorf ( "Unable to bind flag %s: %v" , flg . Name , err )
}
2023-06-23 04:08:00 +03:00
2023-06-30 22:49:58 +03:00
if flg . Changed {
2025-09-30 05:27:54 +03:00
val , err := parseFlagValue ( flg )
if err != nil {
logrus . Errorf ( "Unable to parse flag %s: %v" , flg . Name , err )
return
}
vpr . Set ( configName , val )
2023-06-23 04:08:00 +03:00
}
} )
}
2025-09-30 05:27:54 +03:00
func parseFlagValue ( flg * pflag . Flag ) ( interface { } , error ) {
switch flg . Value . Type ( ) {
case "string" :
return flg . Value . String ( ) , nil
case "bool" :
return strconv . ParseBool ( flg . Value . String ( ) )
case "int" :
return strconv . Atoi ( flg . Value . String ( ) )
default :
return flg . Value . String ( ) , nil
}
}
2023-06-23 04:08:00 +03:00
// Initialize Viper
func initializeConfig ( cmd * cobra . Command ) error {
v := viper . New ( )
// Base name of the config file, without the file extension
v . SetConfigName ( defaultConfigFilename )
v . AddConfigPath ( "." )
2025-09-30 05:27:54 +03:00
// 1. Config file (lowest priority). Return an error if we cannot parse the config file.
2023-06-30 22:49:58 +03:00
err := v . ReadInConfig ( )
if err != nil {
2024-05-06 01:13:07 +03:00
err = fmt . Errorf ( "cannot read config: %v" , err )
2023-06-30 22:49:58 +03:00
logrus . Warn ( err )
2023-06-23 04:08:00 +03:00
}
2025-09-30 05:27:54 +03:00
// 2. Environment variables (medium priority). Bind environment variables to their equivalent keys with underscores
2023-06-30 22:49:58 +03:00
for _ , key := range v . AllKeys ( ) {
envKey := envPrefix + "_" + strings . ToUpper ( strings . ReplaceAll ( key , "." , "_" ) )
err := v . BindEnv ( key , envKey )
if err != nil {
logrus . Errorf ( "Unable to bind ENV valye: %v" , err )
}
}
2023-06-23 04:08:00 +03:00
2025-09-30 05:27:54 +03:00
// 3. Command flags (highest priority). Bind the current command's flags to viper
2023-06-23 04:08:00 +03:00
bindFlags ( cmd , v )
2023-06-30 22:49:58 +03:00
// Dump Viper values to config struct
err = v . Unmarshal ( & config )
if err != nil {
2024-05-06 01:13:07 +03:00
return fmt . Errorf ( "cannot unmarshall config: %v" , err )
2023-06-30 22:49:58 +03:00
}
if config . App . IsDebug {
logrus . Debug ( "Viper config:" )
v . Debug ( )
}
2023-06-23 04:08:00 +03:00
return nil
}
func init ( ) {
2023-06-30 22:49:58 +03:00
RootCmd . PersistentFlags ( ) . IntVarP ( & config . App . Port , "port" , "p" , 7070 , "Port number to run server" )
RootCmd . PersistentFlags ( ) . StringVarP ( & config . App . Host , "host" , "a" , "127.0.0.1" , "Host address to run server" )
RootCmd . PersistentFlags ( ) . IntVarP ( & config . App . Timeout , "timeout" , "t" , 30 , "Timeout to fail request" )
RootCmd . PersistentFlags ( ) . StringVarP ( & config . App . ConfigPath , "config" , "c" , "" , "Configuration file path" )
2026-03-17 02:18:51 +03:00
RootCmd . PersistentFlags ( ) . StringVarP ( & config . App . BrowserPath , "browser-path" , "" , "" , "Custom browser binary path (Chrome/Chromium/Edge/Brave..)" )
2023-06-30 22:49:58 +03:00
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . App . IsVerbose , "verbose" , "v" , false , "Use verbose output" )
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . App . IsDebug , "debug" , "d" , false , "Use debug output. Disable headless browser" )
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . App . IsBrowserHead , "head" , "" , false , "Enable browser UI" )
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . App . IsLeakless , "leakless" , "l" , false , "Use leakless mode to insure browser instances are closed after search" )
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . App . IsRawRequests , "raw" , "r" , false , "Disable browser usage, use HTTP requests" )
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . App . IsLeaveHead , "leave" , "" , false , "Leave browser and tabs opened after search is made" )
2024-05-06 01:13:07 +03:00
RootCmd . PersistentFlags ( ) . StringVarP ( & config . Config2Capcha . ApiKey , "2captcha_key" , "" , "" , "2 captcha api key" )
2025-04-09 02:54:42 +08:00
RootCmd . PersistentFlags ( ) . StringVarP ( & config . App . ProxyURL , "proxy" , "x" , "" , "HTTP or Socks5 proxy URL (e.g. http://user:pass@127.0.0.1:8080)" )
2025-08-18 22:49:45 +03:00
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . App . IsStealth , "stealth" , "s" , false , "Use stealth browser plugin" )
2025-08-28 16:35:53 +08:00
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . App . Insecure , "insecure" , "k" , false , "Allow insecure TLS connections" )
2023-06-23 04:08:00 +03:00
}