first commit
This commit is contained in:
commit
84948f84c4
67
README.md
Normal file
67
README.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Intro
|
||||||
|
afr0dita is a web server where you can download and organize your images by tag.
|
||||||
|
You only need to put an URL with the image that you want to download, add tags and then afr0dita will download
|
||||||
|
and save the image associated with the tag, after that you can search images by tag.
|
||||||
|
|
||||||
|
Check changelog.txt to see the current status.
|
||||||
|
|
||||||
|
# How it looks.
|
||||||
|

|
||||||
|
|
||||||
|
# To know..
|
||||||
|
Feel free to change the code in order to adapt the program to your needs.
|
||||||
|
|
||||||
|
During the first run the program:
|
||||||
|
* Creates ~/afr0dita.db file which is the database.
|
||||||
|
* Creates ~/.afr0ditafiles which is the folder to store the images.
|
||||||
|
* Creates a link from ~/.afr0ditafiles to screenshots/ in the same folder where is the executable.
|
||||||
|
* The server uses the port 8001.
|
||||||
|
|
||||||
|
Check that you have permissions to run the program in the port 8001 and also write permissions in screenshots folder in order to
|
||||||
|
download the screenshots if needed.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
```sh
|
||||||
|
git clone https://code.lacashita.com/sml/afr0dita
|
||||||
|
go build afr0dita.go
|
||||||
|
./afr0dita
|
||||||
|
```
|
||||||
|
|
||||||
|
Once started, you can access at http://127.0.0.1:8001
|
||||||
|
|
||||||
|
# Upgrade
|
||||||
|
Just clone the repo with the last version, compile and replace with the latest binary.
|
||||||
|
|
||||||
|
# Install as a service.
|
||||||
|
|
||||||
|
Create a user to manage the service.
|
||||||
|
```sh
|
||||||
|
/usr/sbin/adduser --system --shell /bin/bash --gecos 'afr0dita Server' --group --disabled-password --home /home/afrodita afrodita
|
||||||
|
```
|
||||||
|
|
||||||
|
Create file /etc/systemd/system/afr0dita.service
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[Unit]
|
||||||
|
Description=afr0dita Server
|
||||||
|
After=syslog.target
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
RestartSec=2s
|
||||||
|
Type=simple
|
||||||
|
User=afrodita
|
||||||
|
Group=afrodita
|
||||||
|
ExecStart=/home/afrodita/afr0dita
|
||||||
|
Restart=always
|
||||||
|
Environment=USER=afrodita HOME=/home/afrodita
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, enable and start the service.
|
||||||
|
```sh
|
||||||
|
sudo systemctl enable afrd0dita
|
||||||
|
sudo systemctl start afr0dita
|
||||||
|
```
|
||||||
382
afr0dita.go
Executable file
382
afr0dita.go
Executable file
@ -0,0 +1,382 @@
|
|||||||
|
/*
|
||||||
|
Description: Tool to save images.
|
||||||
|
Auth0r: sml@lacashita.com
|
||||||
|
|
||||||
|
Use it only as educational purpose.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"io"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start Web PART
|
||||||
|
type urlinfo struct {
|
||||||
|
Date string
|
||||||
|
Url string
|
||||||
|
Tags string
|
||||||
|
Screenshot string
|
||||||
|
Separated []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Msj string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var filedb string
|
||||||
|
var homepath = os.Getenv("HOME")
|
||||||
|
if os.Getenv("afr0dita") == "" {
|
||||||
|
filedb = homepath+"/afr0dita.db"
|
||||||
|
} else {
|
||||||
|
filedb = os.Getenv("afr0dita")
|
||||||
|
}
|
||||||
|
// Create ~/.afr0ditafiles folder to save screenshots
|
||||||
|
if _, err := os.Stat(homepath+"/.afr0ditafiles"); os.IsNotExist(err) {
|
||||||
|
err = os.Mkdir(homepath+"/.afr0ditafiles", 0755)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Link ~/.afr0ditafiles to screenshots/ folder.
|
||||||
|
if _, err := os.Stat("screenshots"); os.IsNotExist(err) {
|
||||||
|
cmd := exec.Command("/usr/bin/ln", "-s", homepath+"/.afr0ditafiles", "screenshots")
|
||||||
|
_, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkDB(filedb)
|
||||||
|
sqliteDatabase, _ := sql.Open("sqlite3", filedb)
|
||||||
|
defer sqliteDatabase.Close()
|
||||||
|
startServer(sqliteDatabase)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Func to check if DB exists, if not create it.
|
||||||
|
func checkDB(filedb string) {
|
||||||
|
_, err := os.Stat(filedb)
|
||||||
|
//If file afr0dita.db doesnt exist.
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("[+]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 DB.
|
||||||
|
func createTable(db *sql.DB) {
|
||||||
|
createafr0ditatable := `CREATE TABLE afr0dita (
|
||||||
|
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"date" TEXT,
|
||||||
|
"url" TEXT,
|
||||||
|
"tags" TEXT,
|
||||||
|
"screenshot" TEXT
|
||||||
|
);`
|
||||||
|
statement, err := db.Prepare(createafr0ditatable)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
statement.Exec()
|
||||||
|
log.Println("[+] afr0dita table created")
|
||||||
|
}
|
||||||
|
|
||||||
|
func startServer(db *sql.DB) {
|
||||||
|
// All requests to /screenshots/ will serve the static content of screenshots/
|
||||||
|
fs := http.FileServer(http.Dir("./screenshots"))
|
||||||
|
http.Handle("/screenshots/", http.StripPrefix("/screenshots/", fs))
|
||||||
|
|
||||||
|
http.HandleFunc("/", afr0ditaHandler(db))
|
||||||
|
// All requests to /styles/css will serve static/css/bootstrap.min.css.
|
||||||
|
http.HandleFunc("/styles/css", func(response http.ResponseWriter, request *http.Request) {
|
||||||
|
http.ServeFile(response, request, "static/css/bootstrap.min.css")
|
||||||
|
})
|
||||||
|
|
||||||
|
http.HandleFunc("/add", func(response http.ResponseWriter, request *http.Request) {
|
||||||
|
http.ServeFile(response, request, "static/html/add.html")
|
||||||
|
})
|
||||||
|
|
||||||
|
http.ListenAndServe("127.0.0.1:8001", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func afr0ditaHandler(db *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var repetido int
|
||||||
|
tagtose := r.URL.Query()["tags"]
|
||||||
|
tagtosearch := strings.Join(tagtose ," ")
|
||||||
|
urltoa := r.URL.Query()["url"]
|
||||||
|
urltoadd := strings.Join(urltoa, " ")
|
||||||
|
repetido = dupURL(db,urltoadd)
|
||||||
|
urltodel := r.URL.Query()["delete"]
|
||||||
|
urltodelete := strings.Join(urltodel ," ")
|
||||||
|
|
||||||
|
var homepath = os.Getenv("HOME")
|
||||||
|
filename := getFilename(urltoadd)
|
||||||
|
screenshotname := filename+".jpg"
|
||||||
|
screenshotfullpath := homepath+"/.afr0ditafiles/"+filename+".jpg"
|
||||||
|
|
||||||
|
if urltodelete != "" {
|
||||||
|
imageURLname := getImageName(db,urltodelete)
|
||||||
|
filetodelete := "screenshots/"+imageURLname
|
||||||
|
os.Remove(filetodelete)
|
||||||
|
deleteUrl(db,urltodelete)
|
||||||
|
}
|
||||||
|
|
||||||
|
if urltoadd == "" {
|
||||||
|
displayafr0dita(db, w,tagtosearch)
|
||||||
|
} else {
|
||||||
|
if repetido != 0 {
|
||||||
|
m := Message{"URL is duplicated."}
|
||||||
|
printMessage(w,m)
|
||||||
|
} else {
|
||||||
|
err := archiveURL(urltoadd,screenshotfullpath,w)
|
||||||
|
if err != nil {
|
||||||
|
m := Message{"Error with the image."}
|
||||||
|
printMessage(w,m)
|
||||||
|
} else {
|
||||||
|
insertUrl(db,urltoadd,tagtosearch,screenshotname)
|
||||||
|
m := Message{"URL added successfully."}
|
||||||
|
printMessage(w,m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayafr0dita(db *sql.DB, w http.ResponseWriter,tagtosearch string) {
|
||||||
|
var Arrayurlinfo []urlinfo
|
||||||
|
var Repetidos []string
|
||||||
|
tmpl := template.Must(template.ParseFiles("static/html/main.html"))
|
||||||
|
p := urlinfo{}
|
||||||
|
if tagtosearch == "" {
|
||||||
|
row, err := db.Query("SELECT date,url,tags,screenshot FROM afr0dita")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer row.Close()
|
||||||
|
for row.Next() {
|
||||||
|
var date string
|
||||||
|
var url string
|
||||||
|
var tags string
|
||||||
|
var screenshot string
|
||||||
|
row.Scan( &date, &url, &tags, &screenshot)
|
||||||
|
// s is []string where tags are separated by ,
|
||||||
|
s := strings.Split(tags, ",")
|
||||||
|
//All s elementes are appended to Repetidos []string
|
||||||
|
for _,element := range s {
|
||||||
|
Repetidos = append(Repetidos,element)
|
||||||
|
}
|
||||||
|
p = urlinfo{date,url,tags,screenshot,s}
|
||||||
|
Arrayurlinfo = append(Arrayurlinfo, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tagtosearch != "" {
|
||||||
|
row, err := db.Query("SELECT date,url,tags,screenshot FROM afr0dita where tags like ?","%"+tagtosearch+"%")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer row.Close()
|
||||||
|
for row.Next() {
|
||||||
|
var date string
|
||||||
|
var url string
|
||||||
|
var tags string
|
||||||
|
var screenshot string
|
||||||
|
row.Scan( &date, &url, &tags, &screenshot)
|
||||||
|
var s []string
|
||||||
|
s = strings.Split(tags, ",")
|
||||||
|
for _,element := range s {
|
||||||
|
Repetidos = append(Repetidos,element)
|
||||||
|
}
|
||||||
|
p = urlinfo{date,url,tags,screenshot,s}
|
||||||
|
Arrayurlinfo = append(Arrayurlinfo, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Repetidos store now []string without tags duplicated.
|
||||||
|
Repetidos = removeDuplicates(Repetidos)
|
||||||
|
// Creates the last urlinfo struct with Repetidos.
|
||||||
|
lastp := urlinfo{"","","","",nil}
|
||||||
|
Arrayurlinfo = append(Arrayurlinfo,lastp)
|
||||||
|
tmpl.Execute(w,Arrayurlinfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Func to Print Messages through Web.
|
||||||
|
func printMessage(w http.ResponseWriter, msg Message) {
|
||||||
|
tmpl := template.Must(template.ParseFiles("static/html/message.html"))
|
||||||
|
tmpl.Execute(w, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inserting URL into SQL.
|
||||||
|
func insertUrl(db *sql.DB, url string, tagstoinsert string, screenshotname string) {
|
||||||
|
currentTime := time.Now()
|
||||||
|
date := currentTime.Format("01-02-2006")
|
||||||
|
insertURLSQL := `INSERT INTO afr0dita(date,url,tags,screenshot) VALUES (?,?,?,?)`
|
||||||
|
statement, err := db.Prepare(insertURLSQL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err.Error())
|
||||||
|
}
|
||||||
|
_, err = statement.Exec(date, url, tagstoinsert,screenshotname)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete URL.
|
||||||
|
func deleteUrl(db *sql.DB, url string) {
|
||||||
|
deleteURLSQL := `DELETE FROM afr0dita WHERE url = ?`
|
||||||
|
statement, err := db.Prepare(deleteURLSQL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err.Error())
|
||||||
|
}
|
||||||
|
_, err = statement.Exec(url)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Func to update an URL to add the filename of the screenshot.
|
||||||
|
func updateUrl(db *sql.DB, filename string, url string) {
|
||||||
|
updateURLSQL := `UPDATE afr0dita set screenshot = ? WHERE url = ?`
|
||||||
|
statement, err := db.Prepare(updateURLSQL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err.Error())
|
||||||
|
}
|
||||||
|
_, err = statement.Exec(filename,url)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Func to check if URL is duplicated, it returns an int with the number of times that URL is repeated.
|
||||||
|
func dupURL(db *sql.DB, url string) int{
|
||||||
|
var total int
|
||||||
|
row, err := db.Query("SELECT count(*) as total from afr0dita 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 get the "id" that will be used as imagename from an given URL, it returns the id.
|
||||||
|
func getImageName(db *sql.DB, url string) string{
|
||||||
|
var imagename string
|
||||||
|
row, err := db.Query("SELECT screenshot from afr0dita where url = ?",url)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer row.Close()
|
||||||
|
for row.Next() {
|
||||||
|
var screenshot string
|
||||||
|
row.Scan( &screenshot)
|
||||||
|
imagename = screenshot
|
||||||
|
}
|
||||||
|
return imagename
|
||||||
|
}
|
||||||
|
|
||||||
|
// Func to remove duplicated strings from an slice.
|
||||||
|
func removeDuplicates(strSlice []string) []string {
|
||||||
|
allKeys := make(map[string]bool)
|
||||||
|
list := []string{}
|
||||||
|
for _, item := range strSlice {
|
||||||
|
if _, value := allKeys[item]; !value {
|
||||||
|
allKeys[item] = true
|
||||||
|
list = append(list, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func archiveURL(URL string, fileName string,w http.ResponseWriter) error {
|
||||||
|
// Set http client to make the request with custom User-Agent.
|
||||||
|
client := &http.Client{}
|
||||||
|
req, _ := http.NewRequest("GET", URL, nil)
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0")
|
||||||
|
// Makes request.
|
||||||
|
response, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
// Check the URL and if the mime is an image or not. If not return an error.
|
||||||
|
bytes, err := ioutil.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mimeType := http.DetectContentType(bytes)
|
||||||
|
if mimeType != "image/jpg" && mimeType != "image/jpg" && mimeType != "image/png" && mimeType != "image/bmp" && mimeType != "image/jpeg" {
|
||||||
|
m := Message{"URL is not an image."}
|
||||||
|
printMessage(w,m)
|
||||||
|
err := errors.New("URL is not an image")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If mime is an image makes a request to get the image.
|
||||||
|
response, err = client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode != 200 {
|
||||||
|
m := Message{"Returning not 200 Code"}
|
||||||
|
printMessage(w,m)
|
||||||
|
}
|
||||||
|
//Create a empty file
|
||||||
|
file, err := os.Create(fileName)
|
||||||
|
if err != nil {
|
||||||
|
m := Message{"Error creating the image."}
|
||||||
|
printMessage(w,m)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
//Write the bytes to the file
|
||||||
|
_, err = io.Copy(file, response.Body)
|
||||||
|
if err != nil {
|
||||||
|
m := Message{"Error copying the image."}
|
||||||
|
printMessage(w,m)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
BIN
afr0dita.png
Normal file
BIN
afr0dita.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 478 KiB |
19
go.mod
Normal file
19
go.mod
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
module afrodita
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/chromedp/chromedp v0.8.6
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
|
||||||
|
)
|
||||||
23
go.sum
Normal file
23
go.sum
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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/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-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=
|
||||||
|
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
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=
|
||||||
7
static/css/bootstrap.min.css
vendored
Executable file
7
static/css/bootstrap.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
61
static/html/add.html
Executable file
61
static/html/add.html
Executable file
@ -0,0 +1,61 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="styles/css">
|
||||||
|
|
||||||
|
<title>afr0dita</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #FFFDFA;
|
||||||
|
}
|
||||||
|
input[type="text"], textarea {
|
||||||
|
background-color : #FFFDFA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control-sm {
|
||||||
|
font-family: inherit;
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 2px solid black;
|
||||||
|
outline: 0;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
padding: 7px 0;
|
||||||
|
background: transparent;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="d-flex flex-column min-vh-100 justify-content-center align-items-center">
|
||||||
|
<h1 class="py-5">afr0dita</h1>
|
||||||
|
<form action="/">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-7">
|
||||||
|
<input class="form-control-sm" type="text" autocomplete="off" id="url" name="url" placeholder="URL"><br><br>
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<input class="form-control-sm" type="text" autocomplete="off" id="tags" name="tags" placeholder="tags"><br><br>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row py-3">
|
||||||
|
|
||||||
|
<div class="py-3">
|
||||||
|
<input type="submit" class="btn btn-outline-dark" value="Submit">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
100
static/html/main.html
Executable file
100
static/html/main.html
Executable file
@ -0,0 +1,100 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="styles/css">
|
||||||
|
<title>afr0dita</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #FFFDFA;
|
||||||
|
padding-top: 100px;
|
||||||
|
font-family: "Ubuntu Mono", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
background-color : #FFFDFA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-top {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1030;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-group {
|
||||||
|
margin-right:5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control-sm {
|
||||||
|
font-family: inherit;
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 2px solid black;
|
||||||
|
outline: 0;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
padding: 7px 0;
|
||||||
|
background: transparent;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<nav class="navbar navbar-light fixed-top navbar-masthead">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand">
|
||||||
|
<form action="/add">
|
||||||
|
<input type="submit" class="btn btn-outline-dark" value="+ Add" />
|
||||||
|
</form>
|
||||||
|
</a>
|
||||||
|
<h1>afr0dita</h1>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="container py-5">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<form class="d-flex" action="/">
|
||||||
|
<input class="form-control-sm me-2" autocomplete="off" type="text" name="tags" id="tags" placeholder="Search" aria-label="Search">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
{{range .}}
|
||||||
|
{{ if eq .Screenshot "" }}
|
||||||
|
{{ else }}
|
||||||
|
<div class="col-3 py-2">
|
||||||
|
<a target="_blank" href="screenshots/{{.Screenshot}}"><img class="card" src="screenshots/{{.Screenshot}}" width="300" height="200" ></a>
|
||||||
|
{{range .Separated}}
|
||||||
|
<span class="badge bg-light"><a href="?tags={{.}}">{{.}}</a></span>
|
||||||
|
{{end}}
|
||||||
|
{{ if eq .Url "" }}
|
||||||
|
{{ else }}
|
||||||
|
<a href="/?delete={{.Url}}"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
|
||||||
|
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
|
||||||
|
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
|
||||||
|
</svg></a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
31
static/html/message.html
Executable file
31
static/html/message.html
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="styles/css">
|
||||||
|
|
||||||
|
<title>afr0dita Message</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #FFFDFA;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center hv-100 vh-100">
|
||||||
|
<div class="container text-center">
|
||||||
|
<div>{{.Msj}}</div>
|
||||||
|
<a class="my-3 badge rounded-pill bg-dark" href="/">Back</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user