Commit 2067045
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=