first commit

This commit is contained in:
sML 2022-12-27 10:06:24 +01:00
commit 33a89cae5a
6 changed files with 607 additions and 0 deletions

40
README.md Normal file
View File

@ -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 | <kbd>bkli add -u https://lacashita.com -d "Cool blog"</kbd>|
| Add url with tags | <kbd>bkli add -u https://lacashita.com -d "Cool blog" -t linux,coding,go</kbd>|
| List url by tag | <kbd>bkli list -t linux</kbd>|
| List url by word | <kbd>bkli list -w myword</kbd>|
| List all urls | <kbd>bkli list</kbd>|
| List url by status | <kbd>bkli list -s Open </kbd>|
| Delete url | <kbd>bkli delete <id></kbd>|
| Modify tags | <kbd>bkli mod <id> -t hacking,testing</kbd>|
| Modify description | <kbd>bkli mod <id> -d "My new description"</kbd>|
| Download Url | <kbd>bkli download <id></kbd>|
| View image if downloaded| <kbd>bkli view <id></kbd>

BIN
bkli Executable file

Binary file not shown.

BIN
bkli.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

497
bkli.go Normal file
View File

@ -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 <id>
Modify tags: bkli mod <id> -t hacking,testing
Modify description : bkli mod <id> -d "My new description"
Download Url: bkli download <id>
View image if downloaded: bkli view <id>
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 <id>")
}
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 <id>")
}
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 <id>")
}
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)
}
}

23
go.mod Normal file
View File

@ -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
)

47
go.sum Normal file
View File

@ -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=