Commit 2067045

Karn Wong <[email protected]>
2025-12-28 17:51:59
feat(getSiteinfo): add siteinfo
1 parent 9f24a8d
Changed files (6)
cmd/get/get_siteinfo.go
@@ -0,0 +1,19 @@
+package get
+
+import (
+	"github.com/kahnwong/swissknife/internal/get"
+	"github.com/spf13/cobra"
+)
+
+var siteinfoCmd = &cobra.Command{
+	Use:   "siteinfo [url]",
+	Short: "Get website technology information",
+	Long:  `Analyze a website and display the technologies it uses, categorized by type`,
+	Run: func(cmd *cobra.Command, args []string) {
+		get.GetSiteInfo(args)
+	},
+}
+
+func init() {
+	Cmd.AddCommand(siteinfoCmd)
+}
internal/generate/qrcode.go
@@ -2,36 +2,12 @@ package generate
 
 import (
 	"fmt"
-	"os"
-	"strings"
 
 	"github.com/kahnwong/swissknife/internal/utils"
 	"github.com/rs/zerolog/log"
 	qrcode "github.com/skip2/go-qrcode"
 )
 
-func setURL(args []string) string {
-	var url string
-	if len(args) == 0 {
-		urlFromClipboard := utils.ReadFromClipboard()
-		if urlFromClipboard != "" {
-			if strings.HasPrefix(urlFromClipboard, "https://") {
-				url = urlFromClipboard
-			}
-		}
-	}
-	if url == "" {
-		if len(args) == 0 {
-			fmt.Println("Please specify URL")
-			os.Exit(1)
-		} else if len(args) == 1 {
-			url = args[0]
-		}
-	}
-
-	return url
-}
-
 func generateQRCode(url string) ([]byte, string) {
 	// init
 	var q *qrcode.QRCode
@@ -55,7 +31,7 @@ func generateQRCode(url string) ([]byte, string) {
 
 func QRCode(args []string) {
 	// set URL
-	url := setURL(args)
+	url := utils.SetURL(args)
 	fmt.Println(url)
 
 	// main
internal/get/siteinfo.go
@@ -0,0 +1,179 @@
+package get
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"sort"
+	"strings"
+
+	"github.com/kahnwong/swissknife/configs/color"
+	"github.com/kahnwong/swissknife/internal/utils"
+	wappalyzer "github.com/projectdiscovery/wappalyzergo"
+	"github.com/rs/zerolog/log"
+)
+
+// categoryMapping maps wappalyzer category names to our display categories
+var categoryMapping = map[string]string{
+	"CMS":                             "CMS",
+	"Ecommerce":                       "Ecommerce",
+	"Analytics":                       "Analytics",
+	"Blogs":                           "CMS",
+	"JavaScript frameworks":           "JavaScript Frameworks",
+	"Web frameworks":                  "Web Frameworks",
+	"Web servers":                     "Web Servers",
+	"CDN":                             "CDN",
+	"Caching":                         "Caching",
+	"Programming languages":           "Programming Languages",
+	"Operating systems":               "Operating Systems",
+	"Databases":                       "Databases",
+	"Message boards":                  "CMS",
+	"Payment processors":              "Payment",
+	"Security":                        "Security",
+	"Tag managers":                    "Analytics",
+	"Marketing automation":            "Marketing",
+	"Advertising":                     "Marketing",
+	"SEO":                             "Marketing",
+	"Live chat":                       "Communication",
+	"Font scripts":                    "UI",
+	"Mobile frameworks":               "Mobile",
+	"PaaS":                            "Cloud",
+	"IaaS":                            "Cloud",
+	"Reverse proxies":                 "Infrastructure",
+	"Load balancers":                  "Infrastructure",
+	"Web server extensions":           "Web Servers",
+	"JavaScript libraries":            "JavaScript Frameworks",
+	"UI frameworks":                   "UI",
+	"Hosting panels":                  "Hosting",
+	"Comment systems":                 "Communication",
+	"Widgets":                         "UI",
+	"Video players":                   "Media",
+	"Maps":                            "Maps",
+	"Remote access":                   "Infrastructure",
+	"Network devices":                 "Infrastructure",
+	"Control systems":                 "Infrastructure",
+	"Static site generator":           "Static Site Generator",
+	"Development":                     "Development",
+	"CI":                              "Development",
+	"Page builders":                   "CMS",
+	"Wikis":                           "CMS",
+	"Documentation":                   "Documentation",
+	"Issue trackers":                  "Development",
+	"Photo galleries":                 "Media",
+	"LMS":                             "Education",
+	"Rich text editors":               "UI",
+	"Editors":                         "UI",
+	"Search engines":                  "Search",
+	"Feed readers":                    "Content",
+	"DMS":                             "Content",
+	"Webmail":                         "Communication",
+	"CRM":                             "Business",
+	"Accounting":                      "Business",
+	"User onboarding":                 "UI",
+	"A/B Testing":                     "Marketing",
+	"Accessibility":                   "Accessibility",
+	"Authentication":                  "Security",
+	"Build/Task runners":              "Development",
+	"Containers":                      "Infrastructure",
+	"Cookie compliance":               "Legal",
+	"Cryptominers":                    "Cryptocurrency",
+	"Database managers":               "Databases",
+	"Date & time pickers":             "UI",
+	"DevOps":                          "Development",
+	"Error logging":                   "Monitoring",
+	"Feature management":              "Development",
+	"Geolocation":                     "Location",
+	"GraphQL":                         "API",
+	"Headless CMS":                    "CMS",
+	"Media servers":                   "Media",
+	"Miscellaneous":                   "Miscellaneous",
+	"Monitoring":                      "Monitoring",
+	"Network storage":                 "Storage",
+	"Performance":                     "Performance",
+	"Personalisation":                 "Marketing",
+	"Privacy":                         "Privacy",
+	"Proxies":                         "Infrastructure",
+	"RUM":                             "Performance",
+	"Retargeting":                     "Marketing",
+	"SSL/TLS certificate authorities": "Security",
+	"Search engine crawlers":          "SEO",
+	"Server-side rendering":           "Web Frameworks",
+	"Webcams":                         "Media",
+}
+
+func GetSiteInfo(args []string) {
+	// set URL
+	url := utils.SetURL(args)
+	fmt.Println(url)
+
+	// fetch site
+	resp, err := http.DefaultClient.Get(url)
+	if err != nil {
+		log.Fatal().Err(err).Msgf("Failed to fetch URL: %s", url)
+	}
+	defer func(Body io.ReadCloser) {
+		err = Body.Close()
+		if err != nil {
+			log.Error().Err(err).Msg("Failed to close response body")
+		}
+	}(resp.Body)
+
+	data, err := io.ReadAll(resp.Body)
+	if err != nil {
+		log.Fatal().Err(err).Msg("Failed to read response body")
+	}
+
+	// init wappalyzer
+	wappalyzerClient, err := wappalyzer.New()
+	if err != nil {
+		log.Fatal().Err(err).Msg("Failed to initialize wappalyzer")
+	}
+
+	appsInfo := wappalyzerClient.FingerprintWithInfo(resp.Header, data)
+
+	// categorize tech info
+	categoryTechs := make(map[string][]string)
+	for appName, info := range appsInfo {
+		cleanName := strings.Split(appName, ":")[0]
+
+		for _, wapCat := range info.Categories {
+			displayCat := categoryMapping[wapCat]
+			if displayCat == "" {
+				displayCat = wapCat // Use original if no mapping exists
+			}
+
+			techs := categoryTechs[displayCat]
+			found := false
+			for _, t := range techs {
+				if t == cleanName {
+					found = true
+					break
+				}
+			}
+			if !found {
+				categoryTechs[displayCat] = append(techs, cleanName)
+			}
+		}
+	}
+
+	if len(categoryTechs) == 0 {
+		fmt.Println("No technologies detected")
+	}
+
+	categories := make([]string, 0, len(categoryTechs))
+	for cat := range categoryTechs {
+		categories = append(categories, cat)
+	}
+	sort.Strings(categories)
+
+	// display technologies
+	for _, category := range categories {
+		techs := categoryTechs[category]
+		sort.Strings(techs)
+
+		fmt.Printf("%s:\n", color.Green(category))
+		for _, tech := range techs {
+			fmt.Printf("- %s\n", tech)
+		}
+	}
+}
internal/utils/url.go
@@ -0,0 +1,29 @@
+package utils
+
+import (
+	"fmt"
+	"os"
+	"strings"
+)
+
+func SetURL(args []string) string {
+	var url string
+	if len(args) == 0 {
+		urlFromClipboard := ReadFromClipboard()
+		if urlFromClipboard != "" {
+			if strings.HasPrefix(urlFromClipboard, "https://") {
+				url = urlFromClipboard
+			}
+		}
+	}
+	if url == "" {
+		if len(args) == 0 {
+			fmt.Println("Please specify URL")
+			os.Exit(1)
+		} else if len(args) == 1 {
+			url = args[0]
+		}
+	}
+
+	return url
+}
go.mod
@@ -15,6 +15,7 @@ require (
 	github.com/jaypipes/ghw v0.21.2
 	github.com/jedib0t/go-pretty/v6 v6.7.5
 	github.com/libp2p/go-netroute v0.4.0
+	github.com/projectdiscovery/wappalyzergo v0.2.61
 	github.com/rs/zerolog v1.34.0
 	github.com/sethvargo/go-diceware v0.5.0
 	github.com/sethvargo/go-password v0.3.1
@@ -58,7 +59,7 @@ require (
 	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
 	github.com/yusufpapurcu/wmi v1.2.4 // indirect
 	golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
-	golang.org/x/net v0.47.0 // indirect
+	golang.org/x/net v0.48.0 // indirect
 	golang.org/x/sys v0.39.0 // indirect
 	golang.org/x/text v0.32.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
go.sum
@@ -54,8 +54,6 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/jaypipes/ghw v0.21.0 h1:ClG2xWtYY0c1ud9jZYwVGdSgfCI7AbmZmZyw3S5HHz8=
-github.com/jaypipes/ghw v0.21.0/go.mod h1:GPrvwbtPoxYUenr74+nAnWbardIZq600vJDD5HnPsPE=
 github.com/jaypipes/ghw v0.21.2 h1:woW0lqNMPbYk59sur6thOVM8YFP9Hxxr8PM+JtpUrNU=
 github.com/jaypipes/ghw v0.21.2/go.mod h1:GPrvwbtPoxYUenr74+nAnWbardIZq600vJDD5HnPsPE=
 github.com/jaypipes/pcidb v1.1.1 h1:QmPhpsbmmnCwZmHeYAATxEaoRuiMAJusKYkUncMC0ro=
@@ -93,6 +91,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
 github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/projectdiscovery/wappalyzergo v0.2.61 h1:TxiYJvXqReiscuWKtGKhFx3VxbVVjHOgECNX709AEX4=
+github.com/projectdiscovery/wappalyzergo v0.2.61/go.mod h1:8FtSVcmPRZU0g1euBpdSYEBHIvB7Zz9MOb754ZqZmfU=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -133,28 +133,22 @@ github.com/yumaojun03/dmidecode v0.1.4/go.mod h1:34bbsMNMNjDbijDpRuqd+2ZapDKxvhO
 github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
 github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
 go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
-golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
-golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
 golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
 golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
-golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
-golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
+golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
+golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
-golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
 golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
-golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
-golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
-golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
+golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
+golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
 golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
 golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=