bkli/bkli.go

498 lines
13 KiB
Go
Raw Permalink Normal View History

2022-12-27 10:06:24 +01:00
/*
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)
}
}