Compare commits

...

2 Commits

Author SHA1 Message Date
fd0732e6f0 Merge branch 'main' of ssh://git.miningtcup.me:222/MiningTcup/notes-cli
Merge license with project
2025-08-17 08:33:33 -07:00
714543bbfe Not the initial commit 2025-08-17 08:32:30 -07:00
3 changed files with 232 additions and 0 deletions

18
go.mod Normal file
View File

@@ -0,0 +1,18 @@
module miningtcup.me/notes-cli
go 1.24.6
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/term v0.34.0 // indirect
modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.38.2 // indirect
)

27
go.sum Normal file
View File

@@ -0,0 +1,27 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=

187
main.go Normal file
View File

@@ -0,0 +1,187 @@
package main
import (
// "database/sql"
"bufio"
"crypto/aes"
"crypto/cipher"
"crypto/pbkdf2"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"log"
"net/http"
"net/url"
"os/signal"
"syscall"
"os"
"golang.org/x/term"
_ "modernc.org/sqlite"
)
var (
serverAddress string
username string
password string
dataDir string
dataPath string
cachePath string
scanner bufio.Scanner
)
func check(err error, format string, a ...any) bool {
if err != nil {
log.Fatalf(format, a, err)
return false
}
return true
}
func ReadPassword() (string, error) {
stdin := int(syscall.Stdin)
oldState, err := term.GetState(stdin)
if err != nil {
return "", err
}
defer term.Restore(stdin, oldState)
sigch := make(chan os.Signal, 1)
signal.Notify(sigch, os.Interrupt)
go func() {
<-sigch
term.Restore(stdin, oldState)
os.Exit(1)
}()
password, err := term.ReadPassword(stdin)
if err != nil {
return "", err
}
return string(password), nil
}
func main() {
scanner = *bufio.NewScanner(os.Stdin)
home := os.Getenv("HOME")
dataPath = os.Getenv("XDG_DATA_HOME")
cachePath = os.Getenv("XDG_CACHE_HOME")
if len(dataPath) < 1 {
dataPath = home + "/.local/share"
}
if len(cachePath) < 1 {
cachePath = home + "/.cache"
}
cachePath += "/notes/notes.db"
dataDir = dataPath + "/notes"
dataPath += "/notes/userinfo.json"
// db, err := sql.Open("sqlite", cachePath)
// if err != nil {
// log.Fatal("Error opening sqlite database for notes cache at ", cachePath, ": ", err)
// }
args := os.Args[1:]
var command string
if len(args) < 1 {
command = ""
} else {
command = args[0]
}
switch command {
case "l", "ls", "list", "search", "find":
userDataBytes, err := os.ReadFile(dataPath)
if errors.Is(err, fs.ErrNotExist) {
login()
}
check(err, "Error reading user data at '%s': %s", dataPath)
var userData []string
err = json.Unmarshal(userDataBytes, &userData)
check(err, "Error unmarshalling user data: %s")
serverAddress, username, password = userData[0], userData[1], userData[2]
response, err := http.Get(fmt.Sprintf("https://%s/get?user=%s", serverAddress, url.QueryEscape(username)))
check(err, "Error getting notes from '%s' as '%s': %s", serverAddress, username)
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
check(err, "Error reading response body while getting notes: %s")
var jsonBody [][2]string
err = json.Unmarshal(body, &jsonBody)
check(err, "Error unmarshalling response body: %s")
decryptedBody := make([]string, len(jsonBody))
for i := range len(jsonBody) {
decryptedBody[i] = decrypt(jsonBody[i][1], password)
jsonDecryptedBody := json.Marshal(decryptedBody)
decryptedBody[i]
}
fmt.Println(decryptedBody)
default:
login()
}
}
func login() {
fmt.Println("Welcome to Notes! To get started, choose your server and user.")
fmt.Print("Server Address [notes.miningtcup.me]: ")
_, err := fmt.Scanln(&serverAddress)
if err != nil {
serverAddress = "notes.miningtcup.me"
}
fmt.Print("Username: ")
scanner.Scan()
username = scanner.Text()
fmt.Print("Password: ")
passwordBytes, err := ReadPassword()
password = string(passwordBytes)
check(err, "Error reading user input (password): %s")
jsonData, err := json.Marshal([]string{serverAddress, username, password})
check(err, "Error marshalling server address and credentials: %s")
err = os.Mkdir(dataDir, 0700)
if !errors.Is(err, fs.ErrExist) {
check(err, "Error creating data directory at '%s': %s", dataDir)
}
err = os.WriteFile(dataPath, jsonData, 0600)
check(err, "Error writing to '%s': ", dataPath)
fmt.Println("\nAwesome! To get started, run 'notes add' or 'notes list'")
}
func decrypt(data string, password string) string {
dataBytes, err := base64.StdEncoding.DecodeString(data)
check(err, "Error decoding base64 data: %s")
if len(dataBytes) < 32 {
fmt.Printf("Not enough data (got %d bytes, expected at least 32)\n", len(dataBytes))
return ""
}
iv := dataBytes[0:16]
salt := dataBytes[16:32]
ciphertext := dataBytes[32:]
if len(ciphertext)%aes.BlockSize != 0 {
fmt.Printf("Ciphertext length %d is not a multiple of block size %d\n", len(ciphertext), aes.BlockSize)
return ""
}
key, err := pbkdf2.Key(sha256.New, password, salt, 65536, 32)
check(err, "Error deriving pbkdf2-sha256 key from password: %s")
block, err := aes.NewCipher(key)
mode := cipher.NewCBCDecrypter(block, iv)
plaintext := make([]byte, len(ciphertext))
mode.CryptBlocks(plaintext, ciphertext)
return string(plaintext)
}