commit 33a89cae5a132f7540a0eccbf9d3e17161729914 Author: sML Date: Tue Dec 27 10:06:24 2022 +0100 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..752652d --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# Intro +babil0nia-kli (bkli) can be used to save/organize your favourite urls, URLs are saved into the sqlite3 db and then you can do some +searchs, filtering or download the saved url to a file (jpg). + +Also available bablin0nia-server (bserver), is the same but through web, and the same db can be shared/sync. + +# How it looks. +![](bkli.gif) + +# To know. +Feel free to change the code in order to adapt the program to your needs. + +During the first run the program: +* Creates ~/babil0nia.db file which is the database. +* Creates ~/.babil0niafiles which is the folder to store the web downloaded (as image). + +# Installation +```sh +git clone https://code.lacashita.com/sml/bkli +go build bkli.go +./bkli +``` + +# Usage + +| Description | Command | +| ----------- | ----------- | +| Add url | bkli add -u https://lacashita.com -d "Cool blog"| +| Add url with tags | bkli add -u https://lacashita.com -d "Cool blog" -t linux,coding,go| +| List url by tag | bkli list -t linux| +| List url by word | bkli list -w myword| +| List all urls | bkli list| +| List url by status | bkli list -s Open | +| Delete url | bkli delete | +| Modify tags | bkli mod -t hacking,testing| +| Modify description | bkli mod -d "My new description"| +| Download Url | bkli download | +| View image if downloaded| bkli view + + diff --git a/bkli b/bkli new file mode 100755 index 0000000..86df4e4 Binary files /dev/null and b/bkli differ diff --git a/bkli.gif b/bkli.gif new file mode 100644 index 0000000..db355fd Binary files /dev/null and b/bkli.gif differ diff --git a/bkli.go b/bkli.go new file mode 100644 index 0000000..b035f2a --- /dev/null +++ b/bkli.go @@ -0,0 +1,497 @@ +/* +Description: Tool to save bookmarks. +Auth0r: sml@lacashita.com + +Use it only as educational purpose. +*/ + +package main + +import ( + "flag" + "fmt" + "database/sql" + "log" + "os" + "github.com/fatih/color" + _ "github.com/mattn/go-sqlite3" + "github.com/rodaine/table" + "strconv" + "time" + "github.com/chromedp/chromedp" + "crypto/md5" + "encoding/hex" + "io/ioutil" + "context" +) + +func check(e error) { + if e != nil { + panic(e) + } +} + + +const help = ` + +(ยท)_(o) babil0nia-kli + +Basic help +----------- +Add url: bkli add -u https://lacashita.com -d "Cool blog" +Add url with tags: bkli add -u https://lacashita.com -d "Cool blog" -t linux,coding,go +List url by tag: bkli list -t linux +List url by word: bkli list -w myword +List all urls: bkli list +List url by status: bkli list -s Open +Delete url: bkli delete +Modify tags: bkli mod -t hacking,testing +Modify description : bkli mod -d "My new description" +Download Url: bkli download +View image if downloaded: bkli view + + +Check more help in README.md file. +` + +func main() { + // Get babil0nia_DB env, if doesnt exists create ~/babil0nia.db as database. + var filedb string + var homepath = os.Getenv("HOME") + if os.Getenv("babil0nia_DB") == "" { + filedb = homepath+"/babil0nia.db" + } else { + filedb = os.Getenv("babil0nia_DB") + } + // Create ~/.babil0niafiles folder to save screenshots + if _, err := os.Stat(homepath+"/.babil0niafiles"); os.IsNotExist(err) { + err = os.Mkdir(homepath+"/.babil0niafiles", 0755) + if err != nil { + log.Fatal(err) + } + } + checkDB(filedb) + bklimain(len(os.Args),filedb) +} + +type Url struct { +id *int +date *string +url *string +description *string +tags *string +screenshot *string +} + +// Main function. +func bklimain(arguments int,filedb string){ + var u Url + var existid int + + sqlDB, _ := sql.Open("sqlite3",filedb) + defer sqlDB.Close() + + if len(os.Args) < 2 { + fmt.Println(help) + os.Exit(0) + } + + switch os.Args[1] { + + case "add": + addCommand := flag.NewFlagSet("add", flag.ExitOnError) + u.url = addCommand.String("u", "", "Url") + u.description = addCommand.String("d", "", "Description") + u.tags = addCommand.String("t", "", "Tags") + addCommand.Parse(os.Args[2:]) + var verify int + + if *u.url == "" || *u.description == "" { + fmt.Println("Parameter -u or -d should not be empty") + os.Exit(0) + } else { + // Check if the URL to add exists. + verify = checkDupURL(sqlDB,*u.url) + if verify == 0 { + addUrl(sqlDB,*u.url, *u.description, *u.tags) + } else { + fmt.Println("URL exists.") + os.Exit(0) + } + } + + case "list": + listCommand := flag.NewFlagSet("list", flag.ExitOnError) + u.description = listCommand.String("w", "%", "Word") + u.tags = listCommand.String("t", "%", "Tag") + + listCommand.Parse(os.Args[2:]) + listUrl(sqlDB,*u.description,*u.tags) + + case "mod": + if len(os.Args) > 2{ + modCommand := flag.NewFlagSet("mod", flag.ExitOnError) + u.url = modCommand.String("u", "", "URL") + u.description = modCommand.String("d", "", "Description") + u.tags = modCommand.String("t", "", "Tags") + modCommand.Parse(os.Args[3:]) + + theintid, err := strconv.Atoi(os.Args[2]) + if err != nil { + fmt.Println("Parameter should be the ID.") + os.Exit(0) + } + existid = checkID(sqlDB,theintid) + if existid != 0 { + modURL(sqlDB,*u.url,*u.description,*u.tags,theintid) + } else { + fmt.Println("ID dont exist.") + } + } else { + fmt.Println("Usage: bkli mod ") + } + + case "download": + if len(os.Args) > 2{ + // Convert param 2 into int. + theintid, err := strconv.Atoi(os.Args[2]) + if err != nil { + fmt.Println("Parameter should be the ID.") + os.Exit(0) + } + existid = checkID(sqlDB,theintid) + if existid != 0 { + urltodownload := obtainURL(sqlDB,theintid) + downloadedfile := checkDownload(sqlDB,urltodownload) + filename := getFilename(urltodownload) + var homepath = os.Getenv("HOME") + screenshotfullpath := homepath+"/.babil0niafiles/"+filename+".jpg" + screenshotname := filename+".jpg" + + + if downloadedfile == false { + err2 := archiveURL(urltodownload,screenshotfullpath,screenshotname,sqlDB) + if err2 != nil { + panic(err2) + } + } else { + fmt.Println("URL downloaded in the past.") + } + } else { + fmt.Println("ID dont exist.") + } + } else { + fmt.Println("Usage: bkli download ") + } + + case "delete": + if len(os.Args) > 2{ + // Convert param 2 into int. + theintid, err := strconv.Atoi(os.Args[2]) + if err != nil { + fmt.Println("Parameter should be the ID.") + os.Exit(0) + } + existid = checkID(sqlDB,theintid) + if existid != 0 { + filetodel := obtainDownload(sqlDB,theintid) + if filetodel != "" { + var homepath = os.Getenv("HOME") + filetodelete := homepath+"/.babil0niafiles/"+filetodel + deleteFile(filetodelete) + } + deleteURL(sqlDB,theintid) + } else { + fmt.Println("ID dont exist.") + } + } else { + fmt.Println("Usage: bkli delete ") + } + + default: + fmt.Println(help) + os.Exit(0) + } +} + +// Func to add Url. +func addUrl(db *sql.DB, url string, description string, tags string) { + date := time.Now().Local().String() + insertSQL := `INSERT INTO babil0nia(url, description, tags, date) VALUES (?, ?, ?, ?)` + statement, err := db.Prepare(insertSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(url, description, tags, date) + if err != nil { + log.Fatalln(err.Error()) + } + color.Green("[+] URL added\n") +} + + +// Func to modify Url. +func modURL(db *sql.DB, url string, description string, tags string, id int) { +// Mod url +if url != "" { + modSQL := `UPDATE babil0nia set url = ? where id = ?` + statement, err := db.Prepare(modSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(url,id) + if err != nil { + log.Fatalln(err.Error()) + } +} +// Mod description +if description != "" { + modSQL := `UPDATE babil0nia set description = ? where id = ?` + statement, err := db.Prepare(modSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(description,id) + if err != nil { + log.Fatalln(err.Error()) + } +} +// Mod tags +if tags != "" { + modSQL := `UPDATE babil0nia set tags = ? where id = ?` + statement, err := db.Prepare(modSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(tags,id) + if err != nil { + log.Fatalln(err.Error()) + } +} + +//If all ok prints: +color.Green("[+] URL modified\n") +} + +// Func to show all URLs. +func listUrl(db *sql.DB, description string, tags string) { + headerFmt := color.New(color.FgMagenta, color.Underline).SprintfFunc() + columnFmt := color.New(color.FgGreen).SprintfFunc() + tbl := table.New("ID", "Url", "Description", "Tags","Screenshot") + tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) + row, err := db.Query("SELECT * FROM babil0nia where id like '%' and description like ? and tags like ? ","%"+description+"%","%"+tags+"%") + if err != nil { + log.Fatal(err) + } + defer row.Close() + fmt.Printf("\n") + for row.Next() { + var id int + var date string + var url string + var description string + var tags string + var screenshot string + row.Scan(&id, &date, &url, &description, &tags, &screenshot) + tbl.AddRow(id,url,description,tags,screenshot) + } + +tbl.Print() +fmt.Printf("\n") +} + +// Func to delete Url. +func deleteURL(db *sql.DB, id int) { + deleteTaskSQL := `DELETE FROM babil0nia WHERE id = ?` + statement, err := db.Prepare(deleteTaskSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(id) + if err != nil { + log.Fatalln(err.Error()) + } + color.Green("[+] URL %v deleted successfully.\n",id) +} + +// Func to check if ID exists +func checkID(db *sql.DB, id int ) int{ + var total int + row, err := db.Query("SELECT count(*) as total from babil0nia where id = ?",id) + if err != nil { + log.Fatal(err) + } + defer row.Close() + for row.Next() { + var count int + row.Scan( &count) + total = count + } + return total +} + +// Func to check if Url is duplicated +func checkDupURL(db *sql.DB, url string ) int{ + var total int + row, err := db.Query("SELECT count(*) as total from babil0nia where url = ?",url) + if err != nil { + log.Fatal(err) + } + defer row.Close() + for row.Next() { + var count int + row.Scan( &count) + total = count + } + return total +} + +// Func to check if url has been downloaded. +func checkDownload(db *sql.DB, url string ) bool { + downloadedfile := false + row, err := db.Query("SELECT screenshot from babil0nia where url = ?",url) + if err != nil { + log.Fatal(err) + } + defer row.Close() + for row.Next() { + var screenshot string + row.Scan( &screenshot) + + if screenshot != "" { + { + downloadedfile = true + } + } +} +return downloadedfile +} + +// Func to check if DB file exists, if not, it creates the file. +func checkDB(filedb string) { + _, err := os.Stat(filedb) + //If file htask.db doesnt exist. + if err != nil { + color.Green("[+]Creating Database\n") + file, err := os.Create(filedb) + if err != nil { + log.Fatal(err.Error()) + } + file.Close() + sqlDB, _ := sql.Open("sqlite3",filedb) + defer sqlDB.Close() + createTable(sqlDB) + } + } + +// Func to create Database file/schema. +func createTable(db *sql.DB) { + createbabil0niaTableSQL := `CREATE TABLE babil0nia ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "date" TEXT, + "url" TEXT, + "description" TEXT, + "tags" TEXT, + "screenshot" TEXT + );` + + color.Green("[+]Creating babil0nia table...") + statement, err := db.Prepare(createbabil0niaTableSQL) + if err != nil { + log.Fatal(err.Error()) + } + statement.Exec() + color.Green("[+]babil0nia DB created") +} + +// Func to obtain the url given an ID. +func obtainURL(db *sql.DB, id int) string{ + var result string + row, err := db.Query("SELECT url from babil0nia where id = ?",id) + if err != nil { + log.Fatal(err) + } + defer row.Close() + for row.Next() { + var url string + row.Scan( &url) + result = url + } + return result +} + +// Func to obtain the download filename given an ID. +func obtainDownload(db *sql.DB, id int) string{ + var result string + row, err := db.Query("SELECT screenshot from babil0nia where id = ?",id) + if err != nil { + log.Fatal(err) + } + defer row.Close() + for row.Next() { + var screenshot string + row.Scan( &screenshot) + result = screenshot + } + return result +} + +// Func to create a filename using the md5 hash of the url name. +func getFilename(text string) string { + hash := md5.Sum([]byte(text)) + return hex.EncodeToString(hash[:]) + } + +// Func to take one screenshot from the given URL and save it to a file. It returns error and the name of the saved (screenshot) file. +func archiveURL(urltoarchive string, screenshotfullpath string,screenshotname string,db *sql.DB) (error){ + var fail error + // create context + ctx, cancel := chromedp.NewContext( + context.Background(), + // chromedp.WithDebugf(log.Printf), + ) + defer cancel() + // capture screenshot of an element + var buf []byte + // capture entire browser viewport, returning png with quality=90 + if err := chromedp.Run(ctx, fullScreenshot(urltoarchive, 100, &buf)); err != nil { + fail = err + } + if fail != nil { + fmt.Println("Fallo la descarga de la imagen asi que nos e guarda") + } else { + updateFilename(db,urltoarchive,screenshotname) + if err := ioutil.WriteFile(screenshotfullpath, buf, 0o644); err != nil { + fail = err + } + } + return fail +} + +func fullScreenshot(urlstr string, quality int, res *[]byte) chromedp.Tasks { + return chromedp.Tasks{ + chromedp.Navigate(urlstr), + chromedp.FullScreenshot(res, quality), + } +} + +// Func to add Task. +func updateFilename(db *sql.DB, url string, filename string, ) { + insertSQL := `UPDATE babil0nia set screenshot = ? where url = ?` + statement, err := db.Prepare(insertSQL) + if err != nil { + log.Fatalln(err.Error()) + } + _, err = statement.Exec(filename, url) + if err != nil { + log.Fatalln(err.Error()) + } + color.Green("[+] Download updated.\n") +} + +func deleteFile(filetodelete string){ + e := os.Remove(filetodelete) + if e != nil { + fmt.Printf("File %v was not found",filetodelete) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0b6eb21 --- /dev/null +++ b/go.mod @@ -0,0 +1,23 @@ +module bkli + +go 1.19 + +require ( + github.com/chromedp/chromedp v0.8.6 + github.com/fatih/color v1.13.0 + github.com/mattn/go-sqlite3 v1.14.16 + github.com/rodaine/table v1.0.1 +) + +require ( + github.com/chromedp/cdproto v0.0.0-20220924210414-0e3390be1777 // indirect + github.com/chromedp/sysutil v1.0.0 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gobwas/ws v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.9 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9a6cc4a --- /dev/null +++ b/go.sum @@ -0,0 +1,47 @@ +github.com/chromedp/cdproto v0.0.0-20220924210414-0e3390be1777 h1:nEnjcdmVQjhtQm0RFJxRINMw7lsQ8gidtbpsidiDqpY= +github.com/chromedp/cdproto v0.0.0-20220924210414-0e3390be1777/go.mod h1:5Y4sD/eXpwrChIuxhSr/G20n9CdbCmoerOHnuAf0Zr0= +github.com/chromedp/chromedp v0.8.6 h1:KobeeqR2dpfKSG1prS3Y6+FbffMmGC6xmAobRXA9QEQ= +github.com/chromedp/chromedp v0.8.6/go.mod h1:nBYHoD6YSNzrr82cIeuOzhw1Jo/s2o0QQ+ifTeoCZ+c= +github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= +github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= +github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ= +github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI= +golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=