Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
01b214b29f | ||
|
955aeed8b5 |
15
.gitignore
vendored
15
.gitignore
vendored
@ -1,17 +1,10 @@
|
|||||||
*.swp
|
*.swp
|
||||||
bin/
|
|
||||||
cmd/launcher/launcher
|
cmd/launcher/launcher
|
||||||
cmd/request/request
|
cmd/request/request
|
||||||
cmd/contrib/contrib
|
cmd/contrib/contrib
|
||||||
cmd/visit/visit
|
cmd/visit/visit
|
||||||
|
cmd/signup/signup
|
||||||
cmd/review/review
|
cmd/review/review
|
||||||
cmd/stats/stats
|
cmd/welcome/welcome
|
||||||
external/cmd/signup/signup
|
cmd/createkeyfile/createkeyfile
|
||||||
external/cmd/welcome/welcome
|
cmd/registeruser/registeruser
|
||||||
external/cmd/help/help
|
|
||||||
external/cmd/helpers/emailtouser/emailtouser
|
|
||||||
external/cmd/helpers/createkeyfile/createkeyfile
|
|
||||||
external/cmd/helpers/registeruser/registeruser
|
|
||||||
external/cmd/helpers/appendkeyfile/appendkeyfile
|
|
||||||
share/share
|
|
||||||
share/uploads
|
|
||||||
|
71
Makefile
71
Makefile
@ -1,71 +0,0 @@
|
|||||||
all: cmds external
|
|
||||||
|
|
||||||
install: all
|
|
||||||
cp bin/tma /town/bin/
|
|
||||||
cp bin/launcher /usr/local/bin/town
|
|
||||||
cp bin/stats /town/bin/
|
|
||||||
cp bin/contrib /town/bin/
|
|
||||||
cp bin/con /town/bin/
|
|
||||||
cp bin/request /town/bin/
|
|
||||||
cp bin/appendkeyfile /town/bin/
|
|
||||||
cp bin/createkeyfile /town/bin/
|
|
||||||
cp bin/emailtouser /town/bin/
|
|
||||||
cp bin/registeruser /town/bin/
|
|
||||||
cp bin/help /town/bin/external/
|
|
||||||
cp bin/welcome /town/bin/external/
|
|
||||||
cp bin/signup /town/bin/external/
|
|
||||||
cp bin/review /town/bin/
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf bin
|
|
||||||
|
|
||||||
external: bin/help bin/welcome bin/signup exthelpers
|
|
||||||
|
|
||||||
bin/help: external/cmd/help/main.go bin
|
|
||||||
go build -o bin/help ./external/cmd/help
|
|
||||||
|
|
||||||
bin/welcome: external/cmd/welcome/main.go bin
|
|
||||||
go build -o bin/welcome ./external/cmd/welcome
|
|
||||||
|
|
||||||
bin/signup: external/cmd/signup/main.go bin
|
|
||||||
go build -o bin/signup ./external/cmd/signup
|
|
||||||
|
|
||||||
exthelpers: bin/appendkeyfile bin/createkeyfile bin/emailtouser bin/registeruser
|
|
||||||
|
|
||||||
bin/appendkeyfile: external/cmd/helpers/appendkeyfile/main.go bin
|
|
||||||
go build -o bin/appendkeyfile ./external/cmd/helpers/appendkeyfile
|
|
||||||
|
|
||||||
bin/createkeyfile: external/cmd/helpers/createkeyfile/main.go bin
|
|
||||||
go build -o bin/createkeyfile ./external/cmd/helpers/createkeyfile
|
|
||||||
|
|
||||||
bin/emailtouser: external/cmd/helpers/emailtouser/main.go bin
|
|
||||||
go build -o bin/emailtouser ./external/cmd/helpers/emailtouser
|
|
||||||
|
|
||||||
bin/registeruser: external/cmd/helpers/registeruser/main.go bin
|
|
||||||
go build -o bin/registeruser ./external/cmd/helpers/registeruser
|
|
||||||
|
|
||||||
cmds: bin/launcher bin/stats bin/con bin/contrib bin/request bin/review bin/tma
|
|
||||||
|
|
||||||
bin/launcher: cmd/launcher/main.go bin
|
|
||||||
go build -o bin/launcher ./cmd/launcher
|
|
||||||
|
|
||||||
bin/stats: cmd/stats/main.go bin
|
|
||||||
go build -o bin/stats ./cmd/stats
|
|
||||||
|
|
||||||
bin/contrib: cmd/contrib/main.go bin
|
|
||||||
go build -o bin/contrib ./cmd/contrib
|
|
||||||
|
|
||||||
bin/con: cmd/towncon/main.go cmd/towncon/md/* bin
|
|
||||||
go build -o bin/con ./cmd/towncon
|
|
||||||
|
|
||||||
bin/request: cmd/request/main.go bin
|
|
||||||
go build -o bin/request ./cmd/request
|
|
||||||
|
|
||||||
bin/review: cmd/review/main.go bin
|
|
||||||
go build -o bin/review ./cmd/review
|
|
||||||
|
|
||||||
bin/tma: cmd/tma/main.go bin
|
|
||||||
go build -o bin/tma ./cmd/tma
|
|
||||||
|
|
||||||
bin:
|
|
||||||
mkdir -p bin
|
|
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
@ -70,6 +71,8 @@ func validExec(execPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func submit(opts *contribOpts) error {
|
func submit(opts *contribOpts) error {
|
||||||
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
|
|
||||||
var cmdName string
|
var cmdName string
|
||||||
var category string
|
var category string
|
||||||
var shortDesc string
|
var shortDesc string
|
||||||
|
@ -76,13 +76,12 @@ func main() {
|
|||||||
quit(fmt.Sprintf("file contents look wrong: %s", string(stdin)), 8)
|
quit(fmt.Sprintf("file contents look wrong: %s", string(stdin)), 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = f.Write(stdin)
|
n, err = f.Write(stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
quit(err.Error(), 9)
|
quit(err.Error(), 9)
|
||||||
|
} else if n == 0 {
|
||||||
|
quit("wrote nothing to keyfile", 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = f.WriteString("\n")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
7
cmd/help/main.go
Normal file
7
cmd/help/main.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("TODO")
|
||||||
|
}
|
124
cmd/rename/main.go
Normal file
124
cmd/rename/main.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
|
||||||
|
"git.tilde.town/tildetown/town/towndb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func confirmRename() {
|
||||||
|
fmt.Println("this will kill all of the user's current processes. if you're doing this live you should give them a good contact if things break")
|
||||||
|
|
||||||
|
var text string
|
||||||
|
|
||||||
|
for text != "y\n" {
|
||||||
|
fmt.Print("enter 'y' to continue: ")
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
text, _ = reader.ReadString('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func killUser(name string) (err error) {
|
||||||
|
cmd := exec.Command("pkill", "-SIGKILL", "-u", name)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err = cmd.Run(); err != nil {
|
||||||
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
|
if exiterr.ExitCode() == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func usermod(old_name string, new_name string) (err error) {
|
||||||
|
cmd := exec.Command(
|
||||||
|
"usermod",
|
||||||
|
"-l", new_name,
|
||||||
|
"-m",
|
||||||
|
"-d", path.Join("/home", new_name),
|
||||||
|
old_name,
|
||||||
|
)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err = cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = exec.Command("groupmod", "-n", new_name, old_name)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err = cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func renameDb(old_name string, new_name string) (err error) {
|
||||||
|
db, err := towndb.ConnectDB()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = towndb.RenameUser(db, old_name, new_name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(text string) {
|
||||||
|
fmt.Println(text)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 3 {
|
||||||
|
quit("usage: rename username new_username")
|
||||||
|
}
|
||||||
|
|
||||||
|
old_name := os.Args[1]
|
||||||
|
new_name := os.Args[2]
|
||||||
|
|
||||||
|
user, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
quit(fmt.Sprintf("couldn't get user info: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Uid != "0" {
|
||||||
|
quit("must be run as root")
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmRename()
|
||||||
|
|
||||||
|
if err := killUser(old_name); err != nil {
|
||||||
|
quit(fmt.Sprintf("pkill failed: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("killed old processes")
|
||||||
|
|
||||||
|
if err := usermod(old_name, new_name); err != nil {
|
||||||
|
quit(fmt.Sprintf("unix user rename failed: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("renamed unix user")
|
||||||
|
|
||||||
|
if err := renameDb(old_name, new_name); err != nil {
|
||||||
|
fmt.Println(fmt.Sprintf("couldn't rename user in /town/var/town.db: %v", err))
|
||||||
|
fmt.Println("something might be amiss with town.db")
|
||||||
|
} else {
|
||||||
|
fmt.Println("renamed user in /town/var/town.db")
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/email"
|
"git.tilde.town/tildetown/town/email"
|
||||||
"git.tilde.town/tildetown/town/invites"
|
"git.tilde.town/tildetown/town/invites"
|
||||||
@ -32,20 +31,20 @@ See you on the server,
|
|||||||
func loadPassword() (string, error) {
|
func loadPassword() (string, error) {
|
||||||
f, err := os.Open("/town/docs/smtp.pw")
|
f, err := os.Open("/town/docs/smtp.pw")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("could not open smtp password file: %w", err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
pw := make([]byte, 100)
|
pw := make([]byte, 100)
|
||||||
|
|
||||||
n, err := f.Read(pw)
|
n, err := f.Read(pw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("could not read smtp password file: %w", err)
|
return "", err
|
||||||
}
|
}
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return "", errors.New("smtp password file was empty")
|
return "", errors.New("read nothing")
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimSpace(string(pw[0:n])), nil
|
return string(pw[0:n]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendInviteEmail(invite invites.Invite) error {
|
func sendInviteEmail(invite invites.Invite) error {
|
||||||
|
@ -153,6 +153,8 @@ func _main() error {
|
|||||||
|
|
||||||
r := newReviewer(signupDB, u.Username)
|
r := newReviewer(signupDB, u.Username)
|
||||||
|
|
||||||
|
rand.Seed(time.Now().Unix())
|
||||||
|
|
||||||
su := models.TownSignup{}
|
su := models.TownSignup{}
|
||||||
|
|
||||||
signups, err := su.All(signupDB)
|
signups, err := su.All(signupDB)
|
||||||
@ -435,8 +437,7 @@ func _main() error {
|
|||||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
// no match or interrupt. who cares
|
// no match or interrupt. who cares
|
||||||
switch exiterr.ExitCode() {
|
switch exiterr.ExitCode() {
|
||||||
case 1:
|
case 1: case 130:
|
||||||
case 130:
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,6 +134,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func _main(l *log.Logger, db *sql.DB) error {
|
func _main(l *log.Logger, db *sql.DB) error {
|
||||||
|
l.Println("starting a session")
|
||||||
pages := tview.NewPages()
|
pages := tview.NewPages()
|
||||||
mainFlex := tview.NewFlex()
|
mainFlex := tview.NewFlex()
|
||||||
input := tview.NewTextArea()
|
input := tview.NewTextArea()
|
||||||
@ -304,6 +305,7 @@ func _main(l *log.Logger, db *sql.DB) error {
|
|||||||
/nod
|
/nod
|
||||||
and pressing enter will cause you to nod. some other verbs: /quit /look`))
|
and pressing enter will cause you to nod. some other verbs: /quit /look`))
|
||||||
case "quit":
|
case "quit":
|
||||||
|
l.Println("got /quit")
|
||||||
app.Stop()
|
app.Stop()
|
||||||
case "look":
|
case "look":
|
||||||
fmt.Fprintln(msgScroll, "")
|
fmt.Fprintln(msgScroll, "")
|
||||||
@ -312,6 +314,8 @@ func _main(l *log.Logger, db *sql.DB) error {
|
|||||||
if !sm.Advance() {
|
if !sm.Advance() {
|
||||||
fmt.Fprintln(msgScroll, "you nod, but nothing happens.")
|
fmt.Fprintln(msgScroll, "you nod, but nothing happens.")
|
||||||
fmt.Fprintln(msgScroll)
|
fmt.Fprintln(msgScroll)
|
||||||
|
} else {
|
||||||
|
l.Println("advancing scene")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -330,7 +334,10 @@ func _main(l *log.Logger, db *sql.DB) error {
|
|||||||
msgScroll.ScrollToEnd()
|
msgScroll.ScrollToEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
defer db.Close()
|
defer func() {
|
||||||
|
l.Println("exiting")
|
||||||
|
db.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||||
switch event.Key() {
|
switch event.Key() {
|
101
cmd/tma/main.go
101
cmd/tma/main.go
@ -1,101 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/towndb"
|
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dbs struct {
|
|
||||||
Invites *sql.DB
|
|
||||||
Signups *sql.DB
|
|
||||||
Users *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func connect() (*dbs, error) {
|
|
||||||
users, err := sql.Open("sqlite3", "/town/var/town.db?mode=r")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
signups, err := sql.Open("sqlite3", "/town/var/signups/signups.db?mode=r")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
invites, err := sql.Open("sqlite3", "/town/var/invites/invites.db?mode=r")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &dbs{
|
|
||||||
Invites: invites,
|
|
||||||
Signups: signups,
|
|
||||||
Users: users,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func _main(argv []string) error {
|
|
||||||
if len(argv) < 3 {
|
|
||||||
return errors.New("want two args (user|email) <lookup>")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch argv[1] {
|
|
||||||
case "user":
|
|
||||||
return userLookup(argv[2])
|
|
||||||
case "email":
|
|
||||||
return emailLookup(argv[2])
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("idk %s", argv[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func userLookup(username string) error {
|
|
||||||
dbs, err := connect()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := dbs.Users.Prepare("SELECT id,created,username,state,admin FROM users WHERE username = ?")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("user select prepare failed: %w", err)
|
|
||||||
}
|
|
||||||
var created string
|
|
||||||
tu := towndb.TownUser{}
|
|
||||||
err = stmt.QueryRow(username).Scan(&tu.ID, &created, &tu.Username, &tu.State, &tu.IsAdmin)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to select user: %w", err)
|
|
||||||
}
|
|
||||||
i, err := strconv.ParseInt(created, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("time what? %w", err)
|
|
||||||
}
|
|
||||||
tu.Created = time.Unix(i, 0)
|
|
||||||
|
|
||||||
fmt.Println("id,created,username,state,admin")
|
|
||||||
fmt.Printf("%d,%s,%s,%s,%v\n", tu.ID, tu.Created, tu.Username, tu.State, tu.IsAdmin)
|
|
||||||
|
|
||||||
// TODO rest
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func emailLookup(address string) error {
|
|
||||||
// TODO
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := _main(os.Args); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
[0;1;35;95m.-[0;1;31;91m''[0;1;33;93m'-[0;1;32;92m.[0m
|
|
||||||
[0;1;35;95m'[0m [0;1;33;93m_[0m [0;1;34;94m\[0m
|
|
||||||
[0;1;35;95m/[0m [0;1;33;93m/[0;1;32;92m`[0m [0;1;36;96m'.[0m [0;1;35;95m\[0m [0;1;32;92m_[0;1;36;96m..[0;1;34;94m._[0m
|
|
||||||
[0;1;31;91m.[0m [0;1;32;92m|[0m [0;1;35;95m\[0m [0;1;31;91m'[0m [0;1;34;94m_[0m [0;1;33;93m_[0m [0;1;32;92m.[0;1;36;96m'[0m [0;1;31;91m'.[0m
|
|
||||||
[0;1;35;95m.[0;1;31;91m|[0m [0;1;33;93m|[0m [0;1;36;96m'[0m [0;1;31;91m|[0m [0;1;32;92m'/[0;1;36;96m\[0m [0;1;35;95m\[0;1;31;91m\[0m [0;1;32;92m//[0;1;36;96m.[0m [0;1;35;95m.-[0;1;31;91m.[0m [0;1;32;92m.[0m
|
|
||||||
[0;1;35;95m.[0;1;31;91m'[0m [0;1;33;93m|_[0;1;32;92m\[0m [0;1;34;94m\[0m [0;1;33;93m/[0m [0;1;32;92m/[0m [0;1;36;96m`[0;1;34;94m\\[0m [0;1;31;91m//[0;1;33;93m\\[0m [0;1;32;92m/[0;1;36;96m/[0m [0;1;34;94m|[0m [0;1;35;95m'[0m [0;1;33;93m'[0m [0;1;36;96m|[0m
|
|
||||||
[0;1;35;95m.[0;1;31;91m'[0m [0;1;36;96m|`[0;1;34;94m.[0m [0;1;31;91m`[0m [0;1;33;93m..[0;1;32;92m'[0m [0;1;36;96m/[0m [0;1;35;95m\[0;1;31;91m`/[0;1;33;93m/[0m [0;1;32;92m\[0;1;36;96m'/[0m [0;1;35;95m|[0m [0;1;31;91m|[0m [0;1;32;92m|[0m [0;1;34;94m|[0m
|
|
||||||
[0;1;31;91m'-[0;1;33;93m-.[0m [0;1;36;96m.-[0;1;34;94m'[0m [0;1;31;91m'-[0;1;33;93m..[0;1;32;92m.-[0;1;36;96m'`[0m [0;1;33;93m\|[0m [0;1;36;96m|[0;1;34;94m/[0m [0;1;31;91m|[0m [0;1;33;93m|[0m [0;1;36;96m|[0m [0;1;35;95m|[0m
|
|
||||||
[0;1;32;92m|[0m [0;1;34;94m|[0m [0;1;32;92m'[0m [0;1;33;93m|[0m [0;1;32;92m|[0m [0;1;34;94m|[0m [0;1;31;91m|[0m
|
|
||||||
[0;1;36;96m|[0m [0;1;35;95m|[0m [0;1;32;92m|[0m [0;1;36;96m|[0m [0;1;35;95m|[0m [0;1;33;93m|[0m
|
|
||||||
[0;1;34;94m|[0m [0;1;31;91m'.[0;1;33;93m'[0m [0;1;36;96m|[0m [0;1;34;94m|[0m [0;1;31;91m|[0m [0;1;32;92m|[0m
|
|
||||||
[0;1;35;95m|[0m [0;1;33;93m/[0m [0;1;34;94m|[0m [0;1;35;95m|[0m [0;1;33;93m|[0m [0;1;36;96m|[0m
|
|
||||||
[0;1;31;91m`[0;1;33;93m'-[0;1;32;92m'[0m [0;1;35;95m'-[0;1;31;91m-'[0m [0;1;32;92m'[0;1;36;96m--[0;1;34;94m'[0m
|
|
||||||
[0;1;36;96m_[0;1;34;94m..[0;1;35;95m._[0m [0;1;36;96m.[0;1;34;94m-'[0;1;35;95m''[0;1;31;91m-.[0m
|
|
||||||
[0;1;36;96m.-[0;1;34;94m'_[0;1;35;95m..[0;1;31;91m._[0;1;33;93m''[0;1;32;92m.[0m [0;1;34;94m'[0m [0;1;31;91m_[0m [0;1;32;92m\[0m
|
|
||||||
[0;1;36;96m.'[0m [0;1;34;94m.[0;1;35;95m'[0m [0;1;32;92m'[0;1;36;96m.\[0;1;34;94m/[0m [0;1;31;91m/`[0m [0;1;33;93m'[0;1;32;92m.[0m [0;1;34;94m\[0m [0;1;31;91m_[0;1;33;93m..[0;1;32;92m._[0m
|
|
||||||
[0;1;36;96m/[0m [0;1;34;94m.[0;1;35;95m'[0m [0;1;34;94m.[0m [0;1;31;91m|[0m [0;1;36;96m\[0m [0;1;35;95m'[0m [0;1;31;91m.[0;1;33;93m'[0m [0;1;34;94m'.[0m
|
|
||||||
[0;1;34;94m.[0m [0;1;35;95m'[0m [0;1;35;95m|[0m [0;1;33;93m'[0m [0;1;35;95m|[0m [0;1;31;91m'[0;1;33;93m.[0m [0;1;36;96m.-[0;1;34;94m.[0m [0;1;31;91m.[0m
|
|
||||||
[0;1;35;95m|[0m [0;1;31;91m|[0m [0;1;31;91m\[0m [0;1;36;96m\[0m [0;1;31;91m/[0m [0;1;33;93m/[0m [0;1;32;92m|[0m [0;1;36;96m'[0m [0;1;35;95m'[0m [0;1;33;93m|[0m
|
|
||||||
[0;1;31;91m|[0m [0;1;33;93m|[0m [0;1;32;92m`.[0m [0;1;34;94m`[0m [0;1;35;95m.[0;1;31;91m.'[0m [0;1;33;93m/[0m [0;1;36;96m|[0m [0;1;34;94m|[0m [0;1;31;91m|[0m [0;1;32;92m|[0m
|
|
||||||
[0;1;33;93m.[0m [0;1;32;92m'[0m [0;1;34;94m'[0;1;35;95m-.[0;1;31;91m..[0;1;33;93m-'[0;1;32;92m`[0m [0;1;34;94m|[0m [0;1;35;95m|[0m [0;1;33;93m|[0m [0;1;36;96m|[0m
|
|
||||||
[0;1;32;92m\[0m [0;1;36;96m'[0;1;34;94m.[0m [0;1;36;96m.[0m [0;1;35;95m|[0m [0;1;31;91m|[0m [0;1;32;92m|[0m [0;1;34;94m|[0m
|
|
||||||
[0;1;34;94m'.[0m [0;1;35;95m`[0;1;31;91m._[0;1;33;93m__[0;1;32;92m__[0;1;36;96m.-[0;1;34;94m'/[0m [0;1;31;91m|[0m [0;1;33;93m|[0m [0;1;36;96m|[0m [0;1;35;95m|[0m
|
|
||||||
[0;1;31;91m`-[0;1;33;93m._[0;1;32;92m__[0;1;36;96m__[0;1;34;94m_[0m [0;1;35;95m/[0m [0;1;33;93m|[0m [0;1;32;92m|[0m [0;1;34;94m|[0m [0;1;31;91m|[0m
|
|
||||||
[0;1;35;95m`[0m [0;1;32;92m|[0m [0;1;36;96m|[0m [0;1;35;95m|[0m [0;1;33;93m|[0m
|
|
||||||
[0;1;36;96m'-[0;1;34;94m-'[0m [0;1;31;91m'[0;1;33;93m--[0;1;32;92m'[0m
|
|
@ -1,32 +0,0 @@
|
|||||||
[0;1;36;96m,-[0;1;34;94m--[0;1;35;95m-,[0m
|
|
||||||
[0;1;36;96m,/[0m [0;1;35;95m.[0;1;31;91m`|[0m [0;1;32;92m,-[0;1;36;96m--[0;1;34;94m-.[0;1;35;95m.[0m [0;1;33;93m,[0;1;32;92m--[0;1;36;96m.[0m
|
|
||||||
[0;1;36;96m,`[0m [0;1;35;95m.[0;1;31;91m'[0m [0;1;33;93m:[0m [0;1;32;92m/[0m [0;1;34;94m/[0m [0;1;31;91m\[0m [0;1;33;93m.[0;1;32;92m--[0;1;36;96m-.[0m [0;1;33;93m,[0;1;32;92m--[0;1;36;96m.'[0;1;34;94m|[0m
|
|
||||||
[0;1;36;96m;[0m [0;1;35;95m;[0m [0;1;32;92m/[0;1;36;96m/[0m [0;1;35;95m.[0m [0;1;32;92m:[0m [0;1;32;92m/.[0m [0;1;36;96m.[0;1;34;94m/|[0m [0;1;31;91m,[0;1;33;93m--[0;1;32;92m,:[0m [0;1;34;94m:[0m [0;1;35;95m|[0m
|
|
||||||
[0;1;36;96m.'[0;1;34;94m__[0;1;35;95m_,[0;1;31;91m/[0m [0;1;32;92m,[0;1;36;96m'.[0m [0;1;35;95m/[0m [0;1;33;93m;[0;1;32;92m.[0m [0;1;36;96m\[0m [0;1;33;93m.-[0;1;32;92m-'[0;1;36;96m.[0m [0;1;34;94m'[0m [0;1;35;95m;[0;1;31;91m,`[0;1;33;93m--[0;1;32;92m.'[0;1;36;96m`|[0m [0;1;35;95m'[0m [0;1;31;91m:[0m
|
|
||||||
[0;1;34;94m|[0m [0;1;31;91m:[0m [0;1;36;96m|[0;1;34;94m.[0m [0;1;31;91m;[0m [0;1;32;92m/[0m [0;1;36;96m`[0m [0;1;34;94m;[0m [0;1;33;93m/[0;1;32;92m__[0;1;36;96m./[0m [0;1;34;94m\[0m [0;1;35;95m:[0m [0;1;31;91m|[0;1;33;93m|[0m [0;1;36;96m:[0m [0;1;34;94m:[0m [0;1;31;91m|[0m [0;1;33;93m|[0m
|
|
||||||
[0;1;35;95m;[0m [0;1;33;93m|[0;1;32;92m.'[0;1;36;96m;[0m [0;1;34;94m;[0;1;35;95m;[0m [0;1;33;93m|[0m [0;1;32;92m;[0m [0;1;36;96m\[0m [0;1;34;94m;[0m [0;1;35;95m|[0m [0;1;31;91m.[0;1;33;93m--[0;1;32;92m'.[0m [0;1;34;94m'[0m [0;1;31;91m\'[0m [0;1;33;93m.[0;1;32;92m:[0m [0;1;34;94m|[0m [0;1;31;91m\[0m [0;1;33;93m|[0m [0;1;32;92m:[0m
|
|
||||||
[0;1;31;91m`-[0;1;33;93m--[0;1;32;92m-'[0m [0;1;34;94m|[0m [0;1;35;95m|[0;1;31;91m|[0m [0;1;32;92m:[0m [0;1;36;96m|[0m [0;1;34;94m;[0m [0;1;35;95m|[0m [0;1;31;91m'[0;1;33;93m/_[0;1;32;92m__[0;1;36;96m/[0m [0;1;34;94m\[0m [0;1;35;95m|[0m [0;1;33;93m'[0m [0;1;32;92m'[0;1;36;96m|[0m [0;1;35;95m:[0m [0;1;31;91m'[0m [0;1;33;93m'[0;1;32;92m;[0m [0;1;36;96m|[0m
|
|
||||||
[0;1;36;96m'[0m [0;1;35;95m:[0m [0;1;31;91m;[0;1;33;93m.[0m [0;1;36;96m|[0m [0;1;34;94m'[0m [0;1;35;95m'[0m [0;1;31;91m'[0m [0;1;33;93m:[0;1;32;92m;[0m [0;1;34;94m\[0m [0;1;35;95m\[0;1;31;91m;[0m [0;1;36;96m:[0;1;34;94m'[0m [0;1;31;91m'[0m [0;1;33;93m;.[0m [0;1;34;94m;[0m
|
|
||||||
[0;1;34;94m|[0m [0;1;31;91m|[0m [0;1;33;93m'[0;1;32;92m'[0m [0;1;34;94m;[0m [0;1;35;95m\[0;1;31;91m;[0m [0;1;33;93m/[0m [0;1;32;92m|[0m [0;1;36;96m\[0m [0;1;35;95m;[0m [0;1;33;93m`[0m [0;1;34;94m|[0;1;35;95m|[0m [0;1;33;93m|[0m [0;1;32;92m|[0m [0;1;36;96m\[0m [0;1;35;95m|[0m
|
|
||||||
[0;1;35;95m'[0m [0;1;33;93m:[0m [0;1;32;92m|[0m [0;1;36;96m\[0m [0;1;35;95m\[0m [0;1;33;93m',[0m [0;1;36;96m/[0m [0;1;35;95m.[0m [0;1;33;93m\[0m [0;1;36;96m.[0;1;34;94m\[0m [0;1;35;95m;[0;1;31;91m'[0m [0;1;32;92m:[0m [0;1;36;96m|[0m [0;1;34;94m;[0m [0;1;35;95m.[0;1;31;91m'[0m
|
|
||||||
[0;1;31;91m;[0m [0;1;32;92m|.[0;1;36;96m'[0m [0;1;35;95m;[0m [0;1;33;93m:[0m [0;1;36;96m/[0m [0;1;31;91m\[0m [0;1;32;92m\[0m [0;1;34;94m'[0m [0;1;35;95m\[0m [0;1;31;91m|[0;1;33;93m|[0m [0;1;36;96m|[0m [0;1;34;94m'`[0;1;35;95m--[0;1;31;91m'[0m
|
|
||||||
[0;1;33;93m'-[0;1;32;92m--[0;1;36;96m'[0m [0;1;31;91m\[0m [0;1;32;92m\[0m [0;1;36;96m.[0;1;34;94m'[0m [0;1;32;92m:[0m [0;1;34;94m'[0m [0;1;35;95m|[0;1;31;91m--[0;1;33;93m"[0m [0;1;32;92m'[0m [0;1;34;94m:[0m [0;1;35;95m|[0m
|
|
||||||
[0;1;32;92m`-[0;1;36;96m--[0;1;34;94m`[0m [0;1;36;96m\[0m [0;1;35;95m\[0m [0;1;31;91m;[0m [0;1;36;96m;[0m [0;1;35;95m|.[0;1;31;91m'[0m
|
|
||||||
[0;1;35;95m'-[0;1;31;91m--[0;1;33;93m"[0m [0;1;34;94m'-[0;1;35;95m--[0;1;31;91m'[0m
|
|
||||||
|
|
||||||
|
|
||||||
[0;1;35;95m,[0;1;31;91m--[0;1;33;93m--[0;1;32;92m..[0m [0;1;36;96m,-[0;1;34;94m-.[0m
|
|
||||||
[0;1;31;91m,-[0;1;33;93m--[0;1;32;92m-.[0;1;36;96m.[0m [0;1;31;91m/[0m [0;1;32;92m/[0m [0;1;34;94m\[0m [0;1;36;96m,-[0;1;34;94m-.[0;1;35;95m'|[0m
|
|
||||||
[0;1;31;91m/[0m [0;1;32;92m/[0m [0;1;34;94m\[0m [0;1;31;91m/[0m [0;1;32;92m.[0m [0;1;35;95m:[0m [0;1;32;92m,-[0;1;36;96m-,[0;1;34;94m:[0m [0;1;35;95m:[0m [0;1;31;91m|[0m
|
|
||||||
[0;1;33;93m|[0m [0;1;36;96m:[0m [0;1;31;91m:[0m [0;1;33;93m.[0m [0;1;36;96m/[0m [0;1;35;95m;.[0m [0;1;33;93m\,[0;1;32;92m`-[0;1;36;96m-.[0;1;34;94m'`[0;1;35;95m|[0m [0;1;31;91m'[0m [0;1;33;93m:[0m
|
|
||||||
[0;1;32;92m.[0m [0;1;34;94m|[0m [0;1;35;95m;[0;1;31;91m.[0m [0;1;33;93m/.[0m [0;1;36;96m;[0m [0;1;35;95m/[0m [0;1;33;93m`[0m [0;1;32;92m;|[0m [0;1;34;94m:[0m [0;1;31;91m:[0m [0;1;33;93m|[0m [0;1;32;92m|[0m
|
|
||||||
[0;1;36;96m.[0m [0;1;35;95m;[0m [0;1;31;91m/-[0;1;33;93m-`[0m [0;1;32;92m;[0m [0;1;34;94m|[0m [0;1;31;91m;[0m [0;1;33;93m\[0m [0;1;32;92m;[0m [0;1;36;96m|:[0m [0;1;35;95m|[0m [0;1;33;93m\[0m [0;1;32;92m|[0m [0;1;36;96m:[0m
|
|
||||||
[0;1;34;94m;[0m [0;1;31;91m|[0m [0;1;33;93m;[0m [0;1;36;96m|[0m [0;1;35;95m:[0m [0;1;33;93m|[0m [0;1;32;92m;[0m [0;1;36;96m|[0m [0;1;34;94m'|[0m [0;1;31;91m:[0m [0;1;33;93m'[0m [0;1;36;96m';[0m [0;1;34;94m|[0m
|
|
||||||
[0;1;35;95m|[0m [0;1;33;93m:[0m [0;1;32;92m|[0m [0;1;34;94m.[0m [0;1;31;91m|[0m [0;1;32;92m'[0m [0;1;36;96m'[0m [0;1;34;94m'[0m [0;1;35;95m:'[0m [0;1;33;93m'[0m [0;1;32;92m;[0;1;36;96m.[0m [0;1;35;95m;[0m
|
|
||||||
[0;1;31;91m.[0m [0;1;32;92m|[0m [0;1;36;96m'_[0;1;34;94m__[0m [0;1;35;95m'[0m [0;1;33;93m;[0m [0;1;36;96m\;[0m [0;1;34;94m/[0m [0;1;31;91m||[0m [0;1;32;92m|[0m [0;1;36;96m|[0m [0;1;34;94m\[0m [0;1;31;91m|[0m
|
|
||||||
[0;1;33;93m'[0m [0;1;36;96m;[0m [0;1;34;94m:[0m [0;1;35;95m.'[0;1;31;91m|[0m [0;1;33;93m\[0m [0;1;36;96m\[0m [0;1;34;94m'[0;1;35;95m,[0m [0;1;31;91m/[0m [0;1;33;93m'[0m [0;1;36;96m:[0m [0;1;34;94m|[0m [0;1;31;91m;[0m [0;1;33;93m.'[0m
|
|
||||||
[0;1;32;92m'[0m [0;1;34;94m|[0m [0;1;35;95m'/[0m [0;1;33;93m:[0m [0;1;32;92m;[0m [0;1;34;94m:[0m [0;1;33;93m/[0m [0;1;32;92m|[0m [0;1;34;94m|[0m [0;1;35;95m'[0;1;31;91m`-[0;1;33;93m-'[0m
|
|
||||||
[0;1;36;96m|[0m [0;1;35;95m:[0m [0;1;33;93m/[0m [0;1;34;94m\[0m [0;1;31;91m\[0m [0;1;33;93m.'[0m [0;1;36;96m'[0m [0;1;35;95m:[0m [0;1;31;91m|[0m
|
|
||||||
[0;1;34;94m\[0m [0;1;31;91m\[0m [0;1;33;93m.[0;1;32;92m'[0m [0;1;35;95m`[0;1;31;91m--[0;1;33;93m-`[0m [0;1;34;94m;[0m [0;1;31;91m|[0;1;33;93m.'[0m
|
|
||||||
[0;1;31;91m`-[0;1;33;93m--[0;1;32;92m`[0m [0;1;35;95m'[0;1;31;91m--[0;1;33;93m-'[0m
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
|||||||
[0;1;35;95m__[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m\\[0;1;36;96m\\[0;1;34;94m\\[0;1;35;95m\\[0;1;31;91m\\[0;1;33;93m\\[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_/[0;1;31;91m\\[0;1;33;93m\\[0;1;32;92m\_[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m\\[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_/[0;1;31;91m\\[0;1;33;93m\_[0m
|
|
||||||
[0;1;31;91m_[0;1;33;93m\/[0;1;32;92m//[0;1;36;96m//[0;1;34;94m//[0;1;35;95m\\[0;1;31;91m\/[0;1;33;93m//[0;1;32;92m//[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m//[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m\/[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m\\[0;1;31;91m\_[0;1;33;93m\/[0;1;32;92m\\[0;1;36;96m\\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m_\[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m_[0m
|
|
||||||
[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_\[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m_/[0;1;33;93m\\[0;1;32;92m\/[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m//[0;1;31;91m\\[0;1;33;93m\_[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_\[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m\/[0;1;36;96m\\[0;1;34;94m\_[0m
|
|
||||||
[0;1;36;96m_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m\/[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m_/[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m_\[0;1;33;93m//[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m__[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m\\[0;1;31;91m\/[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m_\[0;1;34;94m/\[0;1;35;95m\\[0;1;31;91m_[0m
|
|
||||||
[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m__[0;1;34;94m/\[0;1;35;95m\\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m/\[0;1;36;96m\\[0;1;34;94m__[0;1;35;95m_\[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m\/[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m\/[0;1;31;91m\\[0;1;33;93m\_[0m
|
|
||||||
[0;1;31;91m_[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m\\[0;1;31;91m\_[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m/\[0;1;35;95m\\[0;1;31;91m__[0;1;33;93m__[0;1;32;92m\/[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m\/[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m_[0m
|
|
||||||
[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_\[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m//[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m_/[0;1;35;95m\\[0;1;31;91m\_[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m\\[0;1;32;92m\/[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m\\[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m\\[0;1;34;94m\_[0m
|
|
||||||
[0;1;36;96m_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m\/[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m//[0;1;34;94m/\[0;1;35;95m\\[0;1;31;91m\\[0;1;33;93m/_[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m\\[0;1;31;91m\_[0;1;33;93m__[0;1;32;92m\/[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m\\[0;1;31;91m_[0m
|
|
||||||
[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m//[0;1;34;94m/_[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m\/[0;1;31;91m//[0;1;33;93m//[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m\/[0;1;36;96m//[0;1;34;94m__[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m//[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_\[0;1;31;91m//[0;1;33;93m/_[0;1;32;92m__[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m//[0;1;31;91m//[0;1;33;93m__[0m
|
|
||||||
[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m\\[0;1;32;92m\\[0;1;36;96m\\[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m_/[0;1;32;92m\\[0;1;36;96m\\[0;1;34;94m\_[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m/\[0;1;36;96m\\[0;1;34;94m\\[0;1;35;95m__[0;1;31;91m__[0;1;33;93m_/[0;1;32;92m\\[0;1;36;96m\_[0m
|
|
||||||
[0;1;36;96m_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m//[0;1;32;92m//[0;1;36;96m//[0;1;34;94m//[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m/\[0;1;36;96m\\[0;1;34;94m//[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m__[0;1;36;96m\/[0;1;34;94m\\[0;1;35;95m\\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m_[0m
|
|
||||||
[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m_/[0;1;31;91m\\[0;1;33;93m\/[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_/[0;1;36;96m\\[0;1;34;94m\/[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m//[0;1;32;92m\\[0;1;36;96m\_[0;1;34;94m_\[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m\\[0;1;31;91m\_[0m
|
|
||||||
[0;1;31;91m_[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m_/[0;1;33;93m\\[0;1;32;92m\_[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m_/[0;1;34;94m\\[0;1;35;95m\_[0;1;31;91m__[0;1;33;93m__[0;1;32;92m_\[0;1;36;96m//[0;1;34;94m\\[0;1;35;95m\_[0;1;31;91m\/[0;1;33;93m\\[0;1;32;92m\/[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m_\[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m_[0m
|
|
||||||
[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m_\[0;1;32;92m/\[0;1;36;96m\\[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m_\[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m_\[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m_\[0;1;32;92m/\[0;1;36;96m\\[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m\/[0;1;32;92m\\[0;1;36;96m\_[0m
|
|
||||||
[0;1;36;96m_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m\/[0;1;34;94m/\[0;1;35;95m\\[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m__[0;1;36;96m\/[0;1;34;94m\\[0;1;35;95m\_[0;1;31;91m\/[0;1;33;93m/\[0;1;32;92m\\[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m_[0m
|
|
||||||
[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m\/[0;1;31;91m//[0;1;33;93m\\[0;1;32;92m\_[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m\/[0;1;36;96m//[0;1;34;94m\\[0;1;35;95m\_[0;1;31;91m_/[0;1;33;93m\\[0;1;32;92m\_[0;1;36;96m__[0;1;34;94m_\[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m__[0;1;32;92m\/[0;1;36;96m/\[0;1;34;94m\\[0;1;35;95m\\[0;1;31;91m\_[0m
|
|
||||||
[0;1;31;91m_[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m_\[0;1;32;92m//[0;1;36;96m//[0;1;34;94m\\[0;1;35;95m\\[0;1;31;91m\\[0;1;33;93m\\[0;1;32;92m\_[0;1;36;96m__[0;1;34;94m_\[0;1;35;95m//[0;1;31;91m/\[0;1;33;93m\\[0;1;32;92m\\[0;1;36;96m/_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m\\[0;1;32;92m\_[0;1;36;96m__[0;1;34;94m\/[0;1;35;95m/\[0;1;31;91m\\[0;1;33;93m\\[0;1;32;92m_[0m
|
|
||||||
[0;1;32;92m__[0;1;36;96m__[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m__[0;1;32;92m__[0;1;36;96m__[0;1;34;94m_\[0;1;35;95m//[0;1;31;91m//[0;1;33;93m//[0;1;32;92m//[0;1;36;96m/_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m\/[0;1;32;92m//[0;1;36;96m//[0;1;34;94m__[0;1;35;95m__[0;1;31;91m__[0;1;33;93m_\[0;1;32;92m//[0;1;36;96m/_[0;1;34;94m__[0;1;35;95m__[0;1;31;91m\/[0;1;33;93m//[0;1;32;92m//[0;1;36;96m__[0m
|
|
@ -1,10 +0,0 @@
|
|||||||
|
|
||||||
[0;1;33;93md[0;1;32;92m8P[0m
|
|
||||||
[0;1;33;93md8[0;1;32;92m88[0;1;36;96m88[0;1;34;94m8P[0m
|
|
||||||
[0;1;36;96m?8[0;1;34;94m8'[0m [0;1;31;91md[0;1;33;93m88[0;1;32;92m88[0;1;36;96mb[0m [0;1;34;94m?[0;1;35;95m88[0m [0;1;33;93md[0;1;32;92m8P[0m [0;1;34;94md8[0;1;35;95mP[0m [0;1;31;91m8[0;1;33;93m8b[0;1;32;92md8[0;1;36;96m8b[0m [0;1;33;93md8[0;1;32;92m88[0;1;36;96m8b[0m [0;1;34;94md[0;1;35;95m88[0;1;31;91m88[0;1;33;93mb[0m [0;1;36;96m88[0;1;34;94mbd[0;1;35;95m88[0;1;31;91mb[0m
|
|
||||||
[0;1;34;94m88[0;1;35;95mP[0m [0;1;33;93md8[0;1;32;92mP'[0m [0;1;36;96m?[0;1;34;94m88[0m [0;1;35;95md[0;1;31;91m88[0m [0;1;32;92md8[0;1;36;96mP'[0m [0;1;34;94md[0;1;35;95m8P[0;1;31;91m'[0m [0;1;33;93m8[0;1;32;92m8P[0;1;36;96m'[0m [0;1;34;94m?8[0;1;35;95mb[0m [0;1;33;93md[0;1;32;92m8P[0;1;36;96m'[0m [0;1;34;94m`P[0;1;35;95md8[0;1;31;91mP'[0m [0;1;33;93m?[0;1;32;92m88[0m [0;1;34;94m88[0;1;35;95mP'[0m [0;1;31;91m?[0;1;33;93m8b[0m
|
|
||||||
[0;1;35;95m88[0;1;31;91mb[0m [0;1;32;92m88[0;1;36;96mb[0m [0;1;34;94md[0;1;35;95m88[0m [0;1;31;91m?[0;1;33;93m8b[0m [0;1;32;92m,[0;1;36;96m88[0;1;34;94mb[0m [0;1;35;95m,8[0;1;31;91m8'[0m [0;1;32;92md8[0;1;36;96m8[0m [0;1;35;95m88[0;1;31;91mP[0m [0;1;32;92m8[0;1;36;96m8b[0m [0;1;31;91m88[0;1;33;93mb[0m [0;1;32;92md[0;1;36;96m88[0m [0;1;34;94md[0;1;35;95m88[0m [0;1;33;93m8[0;1;32;92m8P[0m
|
|
||||||
[0;1;31;91m`?[0;1;33;93m8b[0m [0;1;36;96m`?[0;1;34;94m88[0;1;35;95m88[0;1;31;91mP'[0m [0;1;33;93m`[0;1;32;92m?8[0;1;36;96m88[0;1;34;94mP'[0;1;35;95m88[0;1;31;91m8P[0;1;33;93m'[0m [0;1;32;92md[0;1;36;96m88[0;1;34;94m'[0m [0;1;31;91m88[0;1;33;93mb[0m [0;1;36;96m`[0;1;34;94m?8[0;1;35;95m88[0;1;31;91mP'[0;1;33;93m`?[0;1;32;92m88[0;1;36;96m88[0;1;34;94mP'[0;1;35;95md8[0;1;31;91m8'[0m [0;1;32;92m8[0;1;36;96m8b[0m
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,521 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"os/user"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/email"
|
|
||||||
"github.com/charmbracelet/glamour"
|
|
||||||
"github.com/charmbracelet/huh"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed banners/*
|
|
||||||
var banners embed.FS
|
|
||||||
|
|
||||||
//go:embed md/*
|
|
||||||
var md embed.FS
|
|
||||||
|
|
||||||
func banner() (string, error) {
|
|
||||||
dirs, err := banners.ReadDir("banners")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
mx := len(dirs)
|
|
||||||
ix := rand.Intn(mx)
|
|
||||||
content, err := banners.ReadFile(path.Join("banners", dirs[ix].Name()))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(content), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func info() error {
|
|
||||||
infoContent, err := md.ReadFile("md/info.md")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
out, err := glamour.Render(string(infoContent), "auto")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmd := exec.Command("/usr/bin/bat")
|
|
||||||
cmd.Stdin = strings.NewReader(out)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func schedule() error {
|
|
||||||
scheduleContent, err := md.ReadFile("md/schedule.md")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r, _ := glamour.NewTermRenderer(
|
|
||||||
// detect background color and pick either the default dark or light theme
|
|
||||||
glamour.WithAutoStyle(),
|
|
||||||
// wrap output at specific width (default is 80)
|
|
||||||
glamour.WithWordWrap(120),
|
|
||||||
)
|
|
||||||
out, err := r.Render(string(scheduleContent))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmd := exec.Command("/usr/bin/bat")
|
|
||||||
cmd.Stdin = strings.NewReader(out)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
type physicalData struct {
|
|
||||||
Arriving string
|
|
||||||
Lodging bool
|
|
||||||
Cats bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type rsvpData struct {
|
|
||||||
Username string
|
|
||||||
Being string
|
|
||||||
PhysicalData *physicalData `yaml:"PhysicalData,omitempty"`
|
|
||||||
Freeform string
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendRSVPEmail(rd rsvpData) error {
|
|
||||||
bs, err := yaml.Marshal(rd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return email.SendLocalEmail("vilmibm", "RSVP TOWNCON25", string(bs))
|
|
||||||
}
|
|
||||||
|
|
||||||
func rsvp(o opts) error {
|
|
||||||
var being string
|
|
||||||
var arriving string
|
|
||||||
lodging := true
|
|
||||||
var freeform string
|
|
||||||
cats := false
|
|
||||||
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewSelect[string]().Title("How will you be attending?").
|
|
||||||
Options(
|
|
||||||
huh.NewOption("Digitally", "digital"),
|
|
||||||
huh.NewOption("Phyiscally", "physical"),
|
|
||||||
).Value(&being)),
|
|
||||||
|
|
||||||
// Physical attendee form
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewInput().Title("What day/time do you think you might arrive?").
|
|
||||||
Value(&arriving),
|
|
||||||
).WithHideFunc(func() bool {
|
|
||||||
return being != "physical"
|
|
||||||
}),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().Title("Will you be staying overnight at vil's house?").
|
|
||||||
Value(&lodging),
|
|
||||||
).WithHideFunc(func() bool {
|
|
||||||
return being != "physical"
|
|
||||||
}),
|
|
||||||
// cats
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().Title("are you allergic to or otherwise upset by cats?").
|
|
||||||
Value(&cats),
|
|
||||||
).WithHideFunc(func() bool {
|
|
||||||
return being != "physical"
|
|
||||||
}),
|
|
||||||
// Catch all freeform
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewText().
|
|
||||||
Title("Anything you want me to know? Any questions?").
|
|
||||||
Value(&freeform))).WithTheme(huh.ThemeBase())
|
|
||||||
|
|
||||||
err := form.Run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var pd *physicalData
|
|
||||||
|
|
||||||
if being == "physical" {
|
|
||||||
pd = &physicalData{
|
|
||||||
Arriving: arriving,
|
|
||||||
Lodging: lodging,
|
|
||||||
Cats: cats,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rd := rsvpData{
|
|
||||||
Username: o.Username,
|
|
||||||
Being: being,
|
|
||||||
Freeform: freeform,
|
|
||||||
PhysicalData: pd}
|
|
||||||
|
|
||||||
if err = sendRSVPEmail(rd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("THANK YOUUUU!U!U!UU!U!!!!~~~!~!~~!!!!!UUU")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type submitData struct {
|
|
||||||
Username string
|
|
||||||
Live bool
|
|
||||||
When string
|
|
||||||
Desc string
|
|
||||||
Length string
|
|
||||||
Interactive bool
|
|
||||||
Freeform string
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendSubmitEmail(sd submitData) error {
|
|
||||||
bs, err := yaml.Marshal(sd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return email.SendLocalEmail("vilmibm", "SUBMISSION TOWNCON25", string(bs))
|
|
||||||
}
|
|
||||||
|
|
||||||
func submit(o opts) error {
|
|
||||||
submitContent, err := md.ReadFile("md/submit.md")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
out, err := glamour.Render(string(submitContent), "auto")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(out)
|
|
||||||
|
|
||||||
submitting := true
|
|
||||||
var live bool
|
|
||||||
var talkWhen string
|
|
||||||
var length string
|
|
||||||
var desc string
|
|
||||||
var freeform string
|
|
||||||
var interactive bool
|
|
||||||
|
|
||||||
notSubmitting := func() bool {
|
|
||||||
return !submitting
|
|
||||||
}
|
|
||||||
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().Title("So...wanna submit a thing?").Value(&submitting),
|
|
||||||
),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().Title("Excellent. Is it a live talk?").Value(&live),
|
|
||||||
).WithHideFunc(notSubmitting),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewInput().Title("During what time frame can you give the talk").
|
|
||||||
Description("please include relevant time zone information!").Value(&talkWhen),
|
|
||||||
).WithHideFunc(func() bool { return !live || !submitting }),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewText().Title("Please describe your submission").
|
|
||||||
Description("Include details like media format, topic, whether it's text or browser based.").Value(&desc),
|
|
||||||
).WithHideFunc(notSubmitting),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewInput().TitleFunc(func() string {
|
|
||||||
if live {
|
|
||||||
return "About how long is your talk?"
|
|
||||||
} else {
|
|
||||||
return "If your piece has a duration (eg audio/video), approximately what is it?"
|
|
||||||
}
|
|
||||||
}, &live).Value(&length),
|
|
||||||
).WithHideFunc(notSubmitting),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().Title("Is your submission interactive?").
|
|
||||||
Description("for example a game or website").
|
|
||||||
Value(&interactive),
|
|
||||||
).WithHideFunc(func() bool { return !submitting || live }),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewText().Title("Anything else I should know?").Value(&freeform),
|
|
||||||
).WithHideFunc(notSubmitting),
|
|
||||||
).WithTheme(huh.ThemeBase())
|
|
||||||
|
|
||||||
if err := form.Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !submitting {
|
|
||||||
fmt.Println("ok! just run the command again if you change your mind~")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sd := submitData{
|
|
||||||
Username: o.Username,
|
|
||||||
Live: live,
|
|
||||||
When: talkWhen,
|
|
||||||
Length: length,
|
|
||||||
Desc: desc,
|
|
||||||
Freeform: freeform,
|
|
||||||
Interactive: interactive,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = sendSubmitEmail(sd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("THANK YOU!! I'll be in touch~")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type euroData struct {
|
|
||||||
Username string
|
|
||||||
Interested bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func euro(o opts) error {
|
|
||||||
euroContent, err := md.ReadFile("md/euro.md")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
out, err := glamour.Render(string(euroContent), "auto")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(out)
|
|
||||||
|
|
||||||
var interested bool
|
|
||||||
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().Title("So...would you be interested?").Value(&interested),
|
|
||||||
)).WithTheme(huh.ThemeBase())
|
|
||||||
|
|
||||||
if err := form.Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ed := euroData{
|
|
||||||
Username: o.Username,
|
|
||||||
Interested: interested,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = sendEuroEmail(ed); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("THANK YOU~!!")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendEuroEmail(ed euroData) error {
|
|
||||||
bs, err := yaml.Marshal(ed)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return email.SendLocalEmail("vilmibm", "TOWNCON25 EURO INTEREST", string(bs))
|
|
||||||
}
|
|
||||||
|
|
||||||
type voteData struct {
|
|
||||||
Username string
|
|
||||||
Days []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func vote(o opts) error {
|
|
||||||
voteContent, err := md.ReadFile("md/vote.md")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
out, err := glamour.Render(string(voteContent), "auto")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(out)
|
|
||||||
|
|
||||||
var selections []string
|
|
||||||
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewMultiSelect[string]().Title("which days work for you?").
|
|
||||||
Options(
|
|
||||||
huh.NewOption("18th", "18th"),
|
|
||||||
huh.NewOption("25th", "25th"),
|
|
||||||
).Value(&selections))).WithTheme(huh.ThemeBase())
|
|
||||||
|
|
||||||
if err := form.Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
vd := voteData{
|
|
||||||
Username: o.Username,
|
|
||||||
Days: selections,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = sendVoteEmail(vd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("THANK YOU~!!")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendVoteEmail(vd voteData) error {
|
|
||||||
bs, err := yaml.Marshal(vd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return email.SendLocalEmail("vilmibm", "TOWNCON25 DAY VOTE", string(bs))
|
|
||||||
}
|
|
||||||
|
|
||||||
type jamData struct {
|
|
||||||
Username string
|
|
||||||
Desc string
|
|
||||||
Physical bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func jam(o opts) error {
|
|
||||||
jamContent, err := md.ReadFile("md/jam.md")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
out, err := glamour.Render(string(jamContent), "auto")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(out)
|
|
||||||
|
|
||||||
submitting := true
|
|
||||||
var desc string
|
|
||||||
var physical bool
|
|
||||||
|
|
||||||
notSubmitting := func() bool {
|
|
||||||
return !submitting
|
|
||||||
}
|
|
||||||
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().Title("So...wanna propose a jam?").Value(&submitting),
|
|
||||||
),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().Title("Excellent. Does it require being in person?").Value(&physical),
|
|
||||||
).WithHideFunc(notSubmitting),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewText().Title("Describe the jam.").
|
|
||||||
Description("Include details like required knowledge or tools").Value(&desc),
|
|
||||||
).WithHideFunc(notSubmitting),
|
|
||||||
).WithTheme(huh.ThemeBase())
|
|
||||||
|
|
||||||
if err := form.Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !submitting {
|
|
||||||
fmt.Println("ok! just run the command again if you change your mind~")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
jd := jamData{
|
|
||||||
Username: o.Username,
|
|
||||||
Desc: desc,
|
|
||||||
Physical: physical,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = sendJamEmail(jd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("THANK YOU!! I'll be in touch~")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendJamEmail(jd jamData) error {
|
|
||||||
bs, err := yaml.Marshal(jd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return email.SendLocalEmail("vilmibm", "JAMPROP TOWNCON25", string(bs))
|
|
||||||
}
|
|
||||||
|
|
||||||
type opts struct {
|
|
||||||
Username string
|
|
||||||
}
|
|
||||||
|
|
||||||
func _main() error {
|
|
||||||
u, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
o := opts{
|
|
||||||
Username: u.Username,
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := banner()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(b)
|
|
||||||
|
|
||||||
fmt.Print(fmt.Sprintf("\t\t^_^_^_^_^_^_^ hi ~%s ^_^_^_^_^_^_^\n", o.Username))
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
var mode string
|
|
||||||
|
|
||||||
// NB: this could be a standalone prompt but when it is, ctrl+c behavior is goofed up.
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewSelect[string]().Title("whadduyu wanna doo?").
|
|
||||||
Options(
|
|
||||||
//huh.NewOption("VOTE ON DAY", "vote"),
|
|
||||||
//huh.NewOption("EUROPE: REGISTER INTEREST", "euro"),
|
|
||||||
huh.NewOption("Get an info dump", "info"),
|
|
||||||
//huh.NewOption("SEE SCHEDULE", "schedule"),
|
|
||||||
huh.NewOption("RSVP", "rsvp"),
|
|
||||||
huh.NewOption("Submit or propose a work (talk, art, etc)", "submit"),
|
|
||||||
huh.NewOption("Propose a creative jam", "jam"),
|
|
||||||
huh.NewOption("Quit", "quit"),
|
|
||||||
).Value(&mode))).WithTheme(huh.ThemeBase())
|
|
||||||
|
|
||||||
err = form.Run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch mode {
|
|
||||||
case "info":
|
|
||||||
return info()
|
|
||||||
case "schedule":
|
|
||||||
return schedule()
|
|
||||||
case "rsvp":
|
|
||||||
return rsvp(o)
|
|
||||||
case "submit":
|
|
||||||
return submit(o)
|
|
||||||
case "jam":
|
|
||||||
return jam(o)
|
|
||||||
case "vote":
|
|
||||||
return vote(o)
|
|
||||||
case "euro":
|
|
||||||
return euro(o)
|
|
||||||
case "quit":
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("wtf '%s'", mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := _main(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, ":( failed: %s\n", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
On the 11th of October there will be an informal tilde town meetup in Europe. The 11th is the actual birthday of the town so it'll be a special little bday party.
|
|
||||||
|
|
||||||
I'm giving a conference talk at an event in Florence October 5-7 so I could train somewhere after to a city that makes sense; Florence, Rome, in Florence or somewhere in Germany are on the table.
|
|
||||||
|
|
||||||
Given those fleeting details, please lmk if you are interested.
|
|
||||||
|
|
||||||
Getting a rough headcount now will help us plan better!
|
|
@ -1,76 +0,0 @@
|
|||||||
# TOWN CON INFORMATION
|
|
||||||
|
|
||||||
TOWN CON is a celebration of tilde.town's 11 year anniversary featuring a day of talks, art, and music by townies.
|
|
||||||
|
|
||||||
this year (2025), town con takes place OCTOBER 18-19th.
|
|
||||||
|
|
||||||
For those wishing to congregate in the non-cyber world, we'll be gathering at ~vilmibm's house in BERKELEY, CALIFORNIA. If you are interested in the physical event please be sure to read through the PHYSICAL EVENT section below.
|
|
||||||
|
|
||||||
## DIGITAL EVENT
|
|
||||||
|
|
||||||
TOWN CON's primary locus of reality will be here on the server in a text-mode environment. This environment will be supplemented with audio/video feeds as needed.
|
|
||||||
|
|
||||||
Works submitted by townies will all be slotted into a viewing schedule to run throughout the 24 hours of the event. The text-mode environment will either show the submitted work or have a pointer off to where it can be consumed in a browser.
|
|
||||||
|
|
||||||
## JAMS
|
|
||||||
|
|
||||||
Throughout the event townies are encouraged to cluster and collaborate on various creative **JAMS**. Re-run this tool and select _Propose a creative jam_ if you want to suggest one. Suggested jams:
|
|
||||||
|
|
||||||
- music jam
|
|
||||||
- game jam
|
|
||||||
- jam jam
|
|
||||||
- ???
|
|
||||||
|
|
||||||
### live music jam
|
|
||||||
|
|
||||||
A benefit of being at ~vil's house is: they have a music studio! It's very small and people will inevitably sleep in it but it means we will have a sound system to plug into and several instruments. There will multiple guitars and electric basses as well as various synthesizers. Feel free to bring your own sound making devices.
|
|
||||||
|
|
||||||
## PHYSICAL EVENT
|
|
||||||
|
|
||||||
**please rsvp to this event by re-running this command and selecting the RSVP option**
|
|
||||||
|
|
||||||
Arrival on the 17th is encouraged! There's nothing structured but it means you can get settled and meet other townies as they show up.
|
|
||||||
|
|
||||||
On the 18th we'll be plugged into the digital event together and on the 19th we'll do field trips.
|
|
||||||
|
|
||||||
### VENUE and LODGING
|
|
||||||
|
|
||||||
The venue this year...is ~vilmibm's house. It's a weird choice but let's roll with it. I'm not going to list my address openly but for planning purposes you can use the North Berkeley BART Station as a map point.
|
|
||||||
|
|
||||||
I'll message my address directly to people as they confirm their attendance.
|
|
||||||
|
|
||||||
Instead of paying for a venue I intend to purchase a fleet of air mattresses and bedding for folks. My house isn't massive but it should be able to fit up to 20 people. There are couches and some private areas for couples. There's also backyard space if you really feel like camping.
|
|
||||||
|
|
||||||
If you don't feel like sleeping in such, ah, cozy arrangements Berkeley features several hotels. Any of the hotels on Shattuck near the University are reasonably priced and walking distance to my place.
|
|
||||||
|
|
||||||
So far town con has cost **$0**. I'll update this as I start buying stuff for the event. If you want to chip in:
|
|
||||||
|
|
||||||
- paypal: `nateksmith@pm.me`
|
|
||||||
- venmo: `@vilmibm`
|
|
||||||
|
|
||||||
### PARKING
|
|
||||||
|
|
||||||
My neighborhood is very easy to park in. I also have a driveway if you want to stow your car in it.
|
|
||||||
|
|
||||||
### FOOD
|
|
||||||
|
|
||||||
Another perk of not renting a venue means I can spend freely on food for us; it's my intention to purchase food piles for each meal during the con for attendees.
|
|
||||||
|
|
||||||
### TRANSPORTATION
|
|
||||||
|
|
||||||
BART goes from the airport to right near my house. I have a car and can provide rides to people, too, if needed as my schedule permits.
|
|
||||||
|
|
||||||
### ACTIVITIES
|
|
||||||
|
|
||||||
The second day of MEAT EDITION is for field trips. Some suggestions:
|
|
||||||
|
|
||||||
- vilmibm-led Internet Archive HQ tour
|
|
||||||
- go play retro video games and look at lots of old computing magazines at a retro game museum
|
|
||||||
- go to a thrift store
|
|
||||||
- go to a museum
|
|
||||||
- go into the woods
|
|
||||||
- just hang out and play synths (field trip of the mind)
|
|
||||||
|
|
||||||
## COMMS
|
|
||||||
|
|
||||||
For any kind of emergency during the event please contact me via my cell phone. My number can be found at `~vilmibm/phone.txt`.
|
|
@ -1,9 +0,0 @@
|
|||||||
# JAMS
|
|
||||||
|
|
||||||
Town Con will be a great time to collaborate with other townies. We'll have a few concurrent jams: drop in/drop out creative sessions organized around a theme or activity.
|
|
||||||
|
|
||||||
Some known jams:
|
|
||||||
|
|
||||||
- game jam
|
|
||||||
- music jam
|
|
||||||
- ???
|
|
@ -1,3 +0,0 @@
|
|||||||
| **user** | **type** | **description** | **pacific time** | **UTC** |
|
|
||||||
|----------|--------------------|-----------------|------------------|---------|
|
|
||||||
| YOU??? | FLOWERS EVERYWHERE | MY GOD | TBD | TBD |
|
|
@ -1,21 +0,0 @@
|
|||||||
# Works
|
|
||||||
|
|
||||||
_every_ townie is encouraged to submit something (or multiple things) to town con. Seriously: please submit anything that can be consumed via a computer in under 60 minutes. Some ideas:
|
|
||||||
|
|
||||||
- a pre-recorded talk
|
|
||||||
- a live talk to be transmitted over a video call
|
|
||||||
- a web page
|
|
||||||
- an essay, short story, or poem
|
|
||||||
- some ascii art
|
|
||||||
- a soft ware
|
|
||||||
- a game for us all to play right then and there
|
|
||||||
- some XML
|
|
||||||
- a picture of a cool rock you found
|
|
||||||
- a picture of your cat
|
|
||||||
- a picture of someone else's cat
|
|
||||||
|
|
||||||
**The whole purpose of this event is to reflect and celebrate the town's vibrant and idiosyncratic community.** No work is too small.
|
|
||||||
|
|
||||||
**The DEADLINE to submit is October 10th, 2025.** I am downright excited to talk to you about ideas and offer suggestions! Please reach out to `vilmibm` via email or come chat about it in `town chat`.
|
|
||||||
|
|
||||||
It's okay to submit something you haven't finished or even started -- I'll confirm right before the event that you still want your work shown.
|
|
@ -1,6 +0,0 @@
|
|||||||
DEAREST TOWNIE...
|
|
||||||
|
|
||||||
Which of these days would personally work for you for town con 2025? it's ok to select both. if none work...i'm sorry :( you can just ctrl-c outta here.
|
|
||||||
|
|
||||||
(note that the European informal meetup is 10/11).
|
|
||||||
|
|
@ -3,11 +3,13 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/charmbracelet/glamour"
|
"github.com/charmbracelet/glamour"
|
||||||
@ -37,6 +39,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func visitRandomUser() error {
|
func visitRandomUser() error {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
usernames, err := getUsernames()
|
usernames, err := getUsernames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -75,7 +78,7 @@ func visitPrompt() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func visitUser(username string) error {
|
func visitUser(username string) error {
|
||||||
files, err := os.ReadDir(filepath.Join("/home", username))
|
files, err := ioutil.ReadDir(filepath.Join("/home", username))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("user is not accepting visitors (could not read user's home directory: %w)", err)
|
return fmt.Errorf("user is not accepting visitors (could not read user's home directory: %w)", err)
|
||||||
}
|
}
|
||||||
@ -87,7 +90,7 @@ func visitUser(username string) error {
|
|||||||
|
|
||||||
path := filepath.Join("/home", username, file.Name())
|
path := filepath.Join("/home", username, file.Name())
|
||||||
|
|
||||||
data, err := os.ReadFile(path)
|
data, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/invites"
|
"git.tilde.town/tildetown/town/invites"
|
||||||
@ -143,7 +142,7 @@ This program is going to exit and you are now free to ssh to town as yourself:
|
|||||||
|
|
||||||
if your public key isn't found by ssh, you'll need to explain to ssh how to find it with:
|
if your public key isn't found by ssh, you'll need to explain to ssh how to find it with:
|
||||||
|
|
||||||
ssh -i "replace with path to private key file" %[1]s@tilde.town
|
ssh -i "replace with path to public key file" %[1]s@tilde.town
|
||||||
|
|
||||||
for help with ssh, see: https://tilde.town/wiki/getting-started/ssh.html
|
for help with ssh, see: https://tilde.town/wiki/getting-started/ssh.html
|
||||||
|
|
||||||
@ -206,16 +205,12 @@ func createUser(data newUserData) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func keyfileText(data newUserData) string {
|
func keyfileText(data newUserData) string {
|
||||||
pkey := data.PubKey
|
|
||||||
if !strings.HasSuffix(pkey, "\n") {
|
|
||||||
pkey += "\n"
|
|
||||||
}
|
|
||||||
header := `########## GREETINGS! ##########
|
header := `########## GREETINGS! ##########
|
||||||
# This file was automatically generated by tilde.town when
|
# Hi! This file was automatically generated by tilde.town when
|
||||||
# your account was created. You can edit it if you want, but we
|
# your account was created. You can edit it if you want, but we
|
||||||
# recommend adding stuff to ~/.ssh/authorized_keys instead.`
|
# recommend adding stuff to ~/.ssh/authorized_keys instead.`
|
||||||
|
|
||||||
return fmt.Sprintf("%s\n%s", header, pkey)
|
return fmt.Sprintf("%s\n%s", header, data.PubKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
@ -1,47 +0,0 @@
|
|||||||
package codes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const codeLen = 32
|
|
||||||
|
|
||||||
func NewCode(email string) string {
|
|
||||||
charset := "abcdefghijklmnopqrztuvwxyz"
|
|
||||||
charset += strings.ToUpper(charset)
|
|
||||||
charset += "0123456789"
|
|
||||||
charset += "`~!@#$%^&*()-=_+[]{}|;:,./<>?"
|
|
||||||
|
|
||||||
code := []byte{}
|
|
||||||
|
|
||||||
max := big.NewInt(int64(len(charset)))
|
|
||||||
for len(code) < codeLen {
|
|
||||||
ix, err := rand.Int(rand.Reader, max)
|
|
||||||
if err != nil {
|
|
||||||
// TODO this is bad but I'm just kind of hoping it doesn't happen...often
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
code = append(code, charset[ix.Int64()])
|
|
||||||
}
|
|
||||||
|
|
||||||
code = append(code, ' ')
|
|
||||||
|
|
||||||
eb := []byte(email)
|
|
||||||
for x := 0; x < len(eb); x++ {
|
|
||||||
code = append(code, eb[x])
|
|
||||||
}
|
|
||||||
|
|
||||||
return base64.StdEncoding.EncodeToString(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Decode(code string) ([]string, error) {
|
|
||||||
decoded, err := base64.StdEncoding.DecodeString(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Split(string(decoded), " "), nil
|
|
||||||
}
|
|
@ -4,15 +4,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"net"
|
|
||||||
"net/mail"
|
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SMTPHost = "smtp.migadu.com"
|
from = "root@tilde.town"
|
||||||
|
SMTPHost = "smtp.zoho.com"
|
||||||
SMTPPort = 465
|
SMTPPort = 465
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,79 +38,61 @@ func NewExternalMailer(pw string) *ExternalMailer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ExternalMailer) Send(address, subj, body string) error {
|
func (m *ExternalMailer) Send(address, subject, body string) error {
|
||||||
from := mail.Address{Name: "Tilde Town Admins", Address: "root@tilde.town"}
|
headers := map[string]string{
|
||||||
to := mail.Address{Name: "", Address: address}
|
"From": from,
|
||||||
|
"To": address,
|
||||||
|
"Subject": subject,
|
||||||
|
}
|
||||||
|
|
||||||
// Setup headers
|
|
||||||
headers := make(map[string]string)
|
|
||||||
headers["From"] = from.String()
|
|
||||||
headers["To"] = to.String()
|
|
||||||
headers["Subject"] = subj
|
|
||||||
|
|
||||||
// Setup message
|
|
||||||
message := ""
|
message := ""
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
message += fmt.Sprintf("%s: %s\r\n", k, v)
|
message += fmt.Sprintf("%s: %s\r\n", k, v)
|
||||||
}
|
}
|
||||||
message += "\r\n" + body
|
message += "\r\n" + body
|
||||||
|
|
||||||
// Connect to the SMTP Server
|
auth := smtp.PlainAuth("", from, m.Password, SMTPHost)
|
||||||
servername := fmt.Sprintf("%s:%d", SMTPHost, SMTPPort)
|
|
||||||
|
|
||||||
host, _, _ := net.SplitHostPort(servername)
|
server := fmt.Sprintf("%s:%d", SMTPHost, SMTPPort)
|
||||||
|
|
||||||
auth := smtp.PlainAuth("", "root@tilde.town", m.Password, host)
|
tlsconf := &tls.Config{
|
||||||
|
|
||||||
// TLS config
|
|
||||||
tlsconfig := &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
ServerName: host,
|
ServerName: server,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here is the key, you need to call tls.Dial instead of smtp.Dial
|
conn, err := tls.Dial("tcp", server, tlsconf)
|
||||||
// for smtp servers running on 465 that require an ssl connection
|
|
||||||
// from the very beginning (no starttls)
|
|
||||||
conn, err := tls.Dial("tcp", servername, tlsconfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed dial: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := smtp.NewClient(conn, host)
|
c, err := smtp.NewClient(conn, SMTPHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to make smtp client: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth
|
|
||||||
if err = c.Auth(auth); err != nil {
|
if err = c.Auth(auth); err != nil {
|
||||||
return fmt.Errorf("failed to make auth: %w", err)
|
return fmt.Errorf("auth failed for smtp: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// To && From
|
if err = c.Mail(from); err != nil {
|
||||||
if err = c.Mail(from.Address); err != nil {
|
return err
|
||||||
return fmt.Errorf("failed to create mail: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = c.Rcpt(to.Address); err != nil {
|
if err = c.Rcpt(address); err != nil {
|
||||||
return fmt.Errorf("failed to add rcpt: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data
|
|
||||||
w, err := c.Data()
|
w, err := c.Data()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send data: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = w.Write([]byte(message))
|
_, err = w.Write([]byte(message))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write: %w", err)
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
err = w.Close()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to close: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Close()
|
||||||
c.Quit()
|
c.Quit()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
1
external/README.md
vendored
1
external/README.md
vendored
@ -1 +0,0 @@
|
|||||||
this folder contains the things that external people can access via ssh: join@tilde.town, welcome@tilde.town, and help@tilde.town
|
|
61
external/cmd/help/email.go
vendored
61
external/cmd/help/email.go
vendored
@ -1,61 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/email"
|
|
||||||
)
|
|
||||||
|
|
||||||
const emailText = `hello!
|
|
||||||
|
|
||||||
You (hopefully) requested to add a new public key to your tilde.town account.
|
|
||||||
|
|
||||||
If you didn't, feel free to ignore this email (or report it to an admin).
|
|
||||||
|
|
||||||
If you did, here is your auth code: %s
|
|
||||||
|
|
||||||
To use this code, please open a terminal and run:
|
|
||||||
|
|
||||||
ssh help@tilde.town
|
|
||||||
|
|
||||||
Follow the instructions there to add your new key and restore access to your account.
|
|
||||||
|
|
||||||
best,
|
|
||||||
~vilmibm`
|
|
||||||
|
|
||||||
func loadPassword() (string, error) {
|
|
||||||
f, err := os.Open("/town/docs/smtp_help.pw")
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("could not open smtp password file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pw := make([]byte, 100)
|
|
||||||
|
|
||||||
n, err := f.Read(pw)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("could not read smtp password file: %w", err)
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
return "", errors.New("smtp password file was empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.TrimSpace(string(pw[0:n])), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendAuthCodeEmail(ac AuthCode) error {
|
|
||||||
pw, err := loadPassword()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
body := fmt.Sprintf(emailText, ac.Code)
|
|
||||||
|
|
||||||
mailer := email.NewExternalMailer(pw)
|
|
||||||
return mailer.Send(
|
|
||||||
ac.Email,
|
|
||||||
"Adding a new tilde.town public key",
|
|
||||||
body)
|
|
||||||
}
|
|
404
external/cmd/help/main.go
vendored
404
external/cmd/help/main.go
vendored
@ -1,404 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/codes"
|
|
||||||
"git.tilde.town/tildetown/town/external/lockingwriter"
|
|
||||||
"git.tilde.town/tildetown/town/sshkey"
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
"github.com/mattn/go-tty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO consider local-only help command for renaming, email mgmt, deleting account
|
|
||||||
|
|
||||||
// TODO put colorscheme, prompting stuff into own packages for use in the other commands. would be good to get off of survey.
|
|
||||||
|
|
||||||
// TODO use new lockingwriter for logging in the other external commands
|
|
||||||
|
|
||||||
func connectDB() (*sql.DB, error) {
|
|
||||||
db, err := sql.Open("sqlite3", "/town/var/codes/codes.db?mode=rw")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return db, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type colorScheme struct {
|
|
||||||
Header func(...string) string
|
|
||||||
Subtitle func(...string) string
|
|
||||||
Prompt func(...string) string
|
|
||||||
Email func(...string) string
|
|
||||||
Option func(...string) string
|
|
||||||
Error func(...string) string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newColorScheme() colorScheme {
|
|
||||||
s2r := func(s lipgloss.Style) func(...string) string {
|
|
||||||
return s.Render
|
|
||||||
}
|
|
||||||
c := func(s string) lipgloss.Color {
|
|
||||||
return lipgloss.Color(s)
|
|
||||||
}
|
|
||||||
s := lipgloss.NewStyle
|
|
||||||
return colorScheme{
|
|
||||||
Header: s2r(s().Bold(true).Foreground(c("#E0B0FF"))),
|
|
||||||
Subtitle: s2r(s().Italic(true).Foreground(c("gray"))),
|
|
||||||
Email: s2r(s().Bold(true).Underline(true)),
|
|
||||||
Prompt: s2r(s().Bold(true).Foreground(c("#00752d"))),
|
|
||||||
Option: s2r(s().Bold(true).Foreground(c("#38747a"))),
|
|
||||||
Error: s2r(s().Bold(true).Foreground(c("#f43124"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Prompter struct {
|
|
||||||
cs colorScheme
|
|
||||||
tty *tty.TTY
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPrompter(tty *tty.TTY, cs colorScheme) *Prompter {
|
|
||||||
return &Prompter{
|
|
||||||
cs: cs,
|
|
||||||
tty: tty,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Prompter) String(prompt string) (string, error) {
|
|
||||||
// TODO assumes blank is no bueno
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var answer string
|
|
||||||
for answer == "" {
|
|
||||||
fmt.Println("")
|
|
||||||
fmt.Println(p.cs.Prompt(prompt))
|
|
||||||
fmt.Println(p.cs.Subtitle("(press enter to submit)"))
|
|
||||||
answer, err = p.tty.ReadString()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("couldn't collect input: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return answer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Prompter) Select(prompt string, opts []string) (int, error) {
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(p.cs.Prompt(prompt))
|
|
||||||
fmt.Println(p.cs.Subtitle("(pick an option using the corresponding number)"))
|
|
||||||
|
|
||||||
chosen := -1
|
|
||||||
for chosen < 0 {
|
|
||||||
fmt.Println()
|
|
||||||
for ix, o := range opts {
|
|
||||||
fmt.Printf("%s: %s\n", p.cs.Option(fmt.Sprintf("%d", ix+1)), o)
|
|
||||||
}
|
|
||||||
r, err := p.tty.ReadRune()
|
|
||||||
if err != nil {
|
|
||||||
return -1, fmt.Errorf("could not collect answer for '%s': %w", prompt, err)
|
|
||||||
}
|
|
||||||
c, err := strconv.Atoi(string(r))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Printf("I could not understand '%s'. Try again, please.\n", string(r))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if c > len(opts) || c == 0 {
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Printf("%s is not an option. Try again, please.\n", string(r))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
chosen = c - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("")
|
|
||||||
|
|
||||||
return chosen, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func _main(cs colorScheme) error {
|
|
||||||
lw := lockingwriter.New()
|
|
||||||
l := log.New(lw, "help: ", log.Ldate|log.Ltime|log.LUTC|log.Lshortfile|log.Lmsgprefix)
|
|
||||||
|
|
||||||
db, err := connectDB()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not connect to database. please let root@tilde.town know about this.")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(cs.Header("Hi, you have reached the tilde town help desk."))
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Please check out the options below.")
|
|
||||||
fmt.Printf("If none of them apply to your case, you can email %s. \n", cs.Email("root@tilde.town"))
|
|
||||||
tty, err := tty.Open()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not open tty: %w", err)
|
|
||||||
}
|
|
||||||
defer tty.Close()
|
|
||||||
|
|
||||||
p := NewPrompter(tty, cs)
|
|
||||||
|
|
||||||
options := []string{
|
|
||||||
"I need to request that a new SSH key be added to my account.",
|
|
||||||
"I have a code from my e-mail to redeem for a new SSH key",
|
|
||||||
"I just want out of here",
|
|
||||||
}
|
|
||||||
c, err := p.Select("What do you need help with?", options)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
}()
|
|
||||||
|
|
||||||
switch c {
|
|
||||||
case 0:
|
|
||||||
return collectEmail(l, db, cs, p)
|
|
||||||
case 1:
|
|
||||||
return redeemCode(l, db, cs, p)
|
|
||||||
case 2:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func emailToUsername(email string) (string, error) {
|
|
||||||
cmd := exec.Command("sudo", "/town/bin/emailtouser", email)
|
|
||||||
stderrBuff := bytes.NewBuffer([]byte{})
|
|
||||||
stdoutBuff := bytes.NewBuffer([]byte{})
|
|
||||||
cmd.Stderr = stderrBuff
|
|
||||||
cmd.Stdout = stdoutBuff
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return "", fmt.Errorf("emailtouser failed with '%s': %w", stderrBuff.String(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.TrimSpace(stdoutBuff.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectEmail(l *log.Logger, db *sql.DB, cs colorScheme, p *Prompter) error {
|
|
||||||
fmt.Println(cs.Header("We can send a authorization code to an email associated with your town account."))
|
|
||||||
email, err := p.String("email to send reset code to?")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(cs.Header("thanks!"))
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Printf("If %s is associated with a town account we'll email an authorization code.\n", cs.Email(email))
|
|
||||||
|
|
||||||
mustHave := []string{"@", "."}
|
|
||||||
found := 0
|
|
||||||
for _, s := range mustHave {
|
|
||||||
if strings.Contains(email, s) {
|
|
||||||
found++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if found != len(mustHave) {
|
|
||||||
l.Printf("corrupt email '%s'", email)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = emailToUsername(email); err != nil {
|
|
||||||
l.Printf("no user for '%s'", email)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
code := codes.NewCode(email)
|
|
||||||
|
|
||||||
ac := &AuthCode{
|
|
||||||
Code: code,
|
|
||||||
Email: email,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = ac.Insert(db); err != nil {
|
|
||||||
l.Printf("database error: %s", err.Error())
|
|
||||||
return errors.New("the database was sad")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = sendAuthCodeEmail(*ac); err != nil {
|
|
||||||
l.Printf("mail send error: %s", err.Error())
|
|
||||||
return errors.New("email sending failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func redeemCode(l *log.Logger, db *sql.DB, cs colorScheme, p *Prompter) error {
|
|
||||||
fmt.Println(cs.Header("redeem an auth code and add a new public key"))
|
|
||||||
c, err := p.String("paste your auth code:")
|
|
||||||
if err != nil {
|
|
||||||
l.Printf("failed to prompt: %s", err.Error())
|
|
||||||
fmt.Println(cs.Error("sorry, I couldn't read that."))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parts, err := codes.Decode(c)
|
|
||||||
if err != nil {
|
|
||||||
l.Printf("failed to decode auth code: %s", err.Error())
|
|
||||||
fmt.Println(cs.Error("sorry, that doesn't look like an auth code..."))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
code := &AuthCode{
|
|
||||||
Code: c,
|
|
||||||
Email: parts[1],
|
|
||||||
}
|
|
||||||
|
|
||||||
err = code.Hydrate(db)
|
|
||||||
if err != nil {
|
|
||||||
l.Printf("hydrate failed: %s", err.Error())
|
|
||||||
return errors.New("the database is sad")
|
|
||||||
}
|
|
||||||
|
|
||||||
if code.Used {
|
|
||||||
fmt.Println(cs.Error("That code has already been redeemed. You'll have to request a new one."))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
username, err := emailToUsername(code.Email)
|
|
||||||
if err != nil {
|
|
||||||
l.Printf("could not find user: %s", err.Error())
|
|
||||||
fmt.Println(cs.Error("That code doesn't seem to match an account."))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Printf("hi, ~%s", username)
|
|
||||||
|
|
||||||
key, err := p.String("paste your new public key:")
|
|
||||||
if err != nil {
|
|
||||||
l.Printf("failed to prompt: %s", err.Error())
|
|
||||||
fmt.Println(cs.Error("sorry, I couldn't read that."))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
valid, err := sshkey.ValidKey(key)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to validate key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
errMsg := fmt.Sprintf("that key is invalid: %s", err.Error())
|
|
||||||
fmt.Println(cs.Error(errMsg))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command("sudo", "--user", username, "/town/bin/appendkeyfile")
|
|
||||||
cmd.Stdin = bytes.NewBufferString(key + "\n")
|
|
||||||
stdoutBuff := bytes.NewBuffer([]byte{})
|
|
||||||
cmd.Stdout = stdoutBuff
|
|
||||||
stderrBuff := bytes.NewBuffer([]byte{})
|
|
||||||
cmd.Stderr = stderrBuff
|
|
||||||
if err = cmd.Run(); err != nil {
|
|
||||||
l.Printf("appendkeyfile failed with '%s', '%s': %s", stderrBuff.String(), stdoutBuff.String(), err.Error())
|
|
||||||
return errors.New("adding to keys file failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = code.MarkUsed(db)
|
|
||||||
if err != nil {
|
|
||||||
l.Printf("failed to mark used: %s", err.Error())
|
|
||||||
return errors.New("database was sad")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("new key added! you should be able to use it to log in.")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cs := newColorScheme()
|
|
||||||
err := _main(cs)
|
|
||||||
defer func() {
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(cs.Header("bye~"))
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(
|
|
||||||
cs.Error(fmt.Sprintf("sorry, something went wrong: %s", err.Error())))
|
|
||||||
fmt.Println("Please let an admin know by emailing a copy of this error to root@tilde.town")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthCode struct {
|
|
||||||
ID int64
|
|
||||||
Code string
|
|
||||||
Email string
|
|
||||||
Used bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AuthCode) Insert(db *sql.DB) error {
|
|
||||||
stmt, err := db.Prepare(`
|
|
||||||
INSERT INTO auth_codes (code, email)
|
|
||||||
VALUES (?, ?)`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
result, err := stmt.Exec(c.Code, c.Email)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
liid, err := result.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.ID = liid
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AuthCode) Hydrate(db *sql.DB) error {
|
|
||||||
stmt, err := db.Prepare(`
|
|
||||||
SELECT id, used
|
|
||||||
FROM auth_codes
|
|
||||||
WHERE code = ? AND email = ?`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
return stmt.QueryRow(c.Code, c.Email).Scan(&c.ID, &c.Used)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *AuthCode) MarkUsed(db *sql.DB) error {
|
|
||||||
if c.ID == 0 {
|
|
||||||
return errors.New("not hydrated")
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := db.Prepare(`
|
|
||||||
UPDATE auth_codes SET used = 1 WHERE id = ?`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
result, err := stmt.Exec(c.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var rowsAffected int64
|
|
||||||
|
|
||||||
if rowsAffected, err = result.RowsAffected(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rowsAffected == 0 {
|
|
||||||
return errors.New("no rows affected")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
59
external/cmd/helpers/appendkeyfile/main.go
vendored
59
external/cmd/helpers/appendkeyfile/main.go
vendored
@ -1,59 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
The purpose of this command is to be run via sudo as an arbitrary user by the "help" user. It is invoked as part of the "i need to add a new public key" flow from "ssh help@tilde.town".
|
|
||||||
|
|
||||||
It's based on the createkeyfile helper and heavily copy pasta'd. They should probably share code or be a single command but I wanted to keep things simple for now.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/user"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
const keyfileName = "authorized_keys2"
|
|
||||||
|
|
||||||
func quit(msg string, code int) {
|
|
||||||
// TODO print to stderr
|
|
||||||
fmt.Println(msg)
|
|
||||||
os.Exit(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
u, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
quit(err.Error(), 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
sshPath := path.Join("/home", u.Username, ".ssh")
|
|
||||||
keyfilePath := path.Join(sshPath, keyfileName)
|
|
||||||
|
|
||||||
f, err := os.OpenFile(keyfilePath, os.O_APPEND|os.O_WRONLY, 0600)
|
|
||||||
if err != nil {
|
|
||||||
quit(fmt.Sprintf("failed to open %s: %s", keyfilePath, err.Error()), 5)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
stdin := make([]byte, 90000) // arbitrary limit
|
|
||||||
|
|
||||||
n, err := os.Stdin.Read(stdin)
|
|
||||||
if err != nil {
|
|
||||||
quit(err.Error(), 6)
|
|
||||||
} else if n == 0 {
|
|
||||||
quit("nothing passed on STDIN", 7)
|
|
||||||
}
|
|
||||||
|
|
||||||
stdin = stdin[0:n]
|
|
||||||
|
|
||||||
n, err = f.Write(stdin)
|
|
||||||
if err != nil {
|
|
||||||
quit(err.Error(), 9)
|
|
||||||
} else if n == 0 {
|
|
||||||
quit("wrote nothing to keyfile", 10)
|
|
||||||
}
|
|
||||||
}
|
|
40
external/cmd/helpers/emailtouser/main.go
vendored
40
external/cmd/helpers/emailtouser/main.go
vendored
@ -1,40 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/towndb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func _main(args []string) error {
|
|
||||||
if len(args) < 2 {
|
|
||||||
return errors.New("need email")
|
|
||||||
}
|
|
||||||
email := args[1]
|
|
||||||
|
|
||||||
db, err := towndb.ConnectDB()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := towndb.UserForEmail(db, email)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if user == nil {
|
|
||||||
return errors.New("email does not correspond to user")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(user.Username)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := _main(os.Args); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
64
external/lockingwriter/lockingwriter.go
vendored
64
external/lockingwriter/lockingwriter.go
vendored
@ -1,64 +0,0 @@
|
|||||||
package lockingwriter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gofrs/flock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// for now this package defines a writer for use with log.New(). It it intended to be used from the external ssh applications. This logger uses a file lock to allow all the various ssh applications to log to the same file.
|
|
||||||
// the correct way to do this is to send log events to a daemon
|
|
||||||
// but i'm trying to cut a corner
|
|
||||||
|
|
||||||
type LockingWriter struct {
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
fp = "/town/var/log/external.log"
|
|
||||||
lp = "/town/var/log/log.lock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func New() *LockingWriter {
|
|
||||||
f, err := os.OpenFile(fp, os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0660)
|
|
||||||
if err != nil && !os.IsExist(err) {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
|
|
||||||
return &LockingWriter{
|
|
||||||
path: fp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LockingWriter) Write(p []byte) (n int, err error) {
|
|
||||||
fl := flock.New(lp)
|
|
||||||
|
|
||||||
var locked bool
|
|
||||||
for !locked {
|
|
||||||
locked, err = fl.TryLock()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
var f *os.File
|
|
||||||
f, err = os.OpenFile(l.path, os.O_APPEND|os.O_WRONLY, 0660)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer f.Close()
|
|
||||||
defer fl.Unlock()
|
|
||||||
|
|
||||||
n, err = f.Write(p)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
//return f.Write(p)
|
|
||||||
}
|
|
34
go.mod
34
go.mod
@ -6,12 +6,9 @@ require (
|
|||||||
github.com/AlecAivazis/survey/v2 v2.3.5
|
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||||
github.com/MakeNowJust/heredoc/v2 v2.0.1
|
github.com/MakeNowJust/heredoc/v2 v2.0.1
|
||||||
github.com/charmbracelet/glamour v0.5.0
|
github.com/charmbracelet/glamour v0.5.0
|
||||||
github.com/charmbracelet/huh v0.5.3
|
github.com/charmbracelet/lipgloss v0.6.0
|
||||||
github.com/charmbracelet/lipgloss v0.13.0
|
|
||||||
github.com/gdamore/tcell/v2 v2.5.3
|
github.com/gdamore/tcell/v2 v2.5.3
|
||||||
github.com/gofrs/flock v0.8.1
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16
|
github.com/mattn/go-sqlite3 v1.14.16
|
||||||
github.com/mattn/go-tty v0.0.5
|
|
||||||
github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c
|
github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c
|
||||||
github.com/spf13/cobra v1.5.0
|
github.com/spf13/cobra v1.5.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
@ -19,42 +16,27 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/catppuccin/go v0.2.0 // indirect
|
|
||||||
github.com/charmbracelet/bubbles v0.19.0 // indirect
|
|
||||||
github.com/charmbracelet/bubbletea v0.27.0 // indirect
|
|
||||||
github.com/charmbracelet/x/ansi v0.2.2 // indirect
|
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
|
||||||
github.com/charmbracelet/x/term v0.2.0 // indirect
|
|
||||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
|
||||||
github.com/gdamore/encoding v1.0.0 // indirect
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
github.com/gorilla/css v1.0.0 // indirect
|
github.com/gorilla/css v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
github.com/microcosm-cc/bluemonday v1.0.17 // indirect
|
github.com/microcosm-cc/bluemonday v1.0.17 // indirect
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
||||||
github.com/muesli/reflow v0.3.0 // indirect
|
github.com/muesli/reflow v0.3.0 // indirect
|
||||||
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
|
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.2 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/yuin/goldmark v1.4.4 // indirect
|
github.com/yuin/goldmark v1.4.4 // indirect
|
||||||
github.com/yuin/goldmark-emoji v1.0.1 // indirect
|
github.com/yuin/goldmark-emoji v1.0.1 // indirect
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
||||||
golang.org/x/sys v0.24.0 // indirect
|
|
||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect
|
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
)
|
)
|
||||||
|
74
go.sum
74
go.sum
@ -1,36 +1,17 @@
|
|||||||
github.com/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ=
|
github.com/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ=
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
||||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
|
||||||
github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A=
|
github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A=
|
||||||
github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM=
|
github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
|
||||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
|
|
||||||
github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
|
||||||
github.com/charmbracelet/bubbles v0.19.0 h1:gKZkKXPP6GlDk6EcfujDK19PCQqRjaJZQ7QRERx1UF0=
|
|
||||||
github.com/charmbracelet/bubbles v0.19.0/go.mod h1:WILteEqZ+krG5c3ntGEMeG99nCupcuIk7V0/zOP0tOA=
|
|
||||||
github.com/charmbracelet/bubbletea v0.27.0 h1:Mznj+vvYuYagD9Pn2mY7fuelGvP0HAXtZYGgRBCbHvU=
|
|
||||||
github.com/charmbracelet/bubbletea v0.27.0/go.mod h1:5MdP9XH6MbQkgGhnlxUqCNmBXf9I74KRQ8HIidRxV1Y=
|
|
||||||
github.com/charmbracelet/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g=
|
github.com/charmbracelet/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g=
|
||||||
github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc=
|
github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc=
|
||||||
github.com/charmbracelet/huh v0.5.3 h1:3KLP4a/K1/S4dq4xFMTNMt3XWhgMl/yx8NYtygQ0bmg=
|
github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY=
|
||||||
github.com/charmbracelet/huh v0.5.3/go.mod h1:OZC3lshuF+/y8laj//DoZdFSHxC51OrtXLJI8xWVouQ=
|
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
|
||||||
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
|
|
||||||
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
|
|
||||||
github.com/charmbracelet/x/ansi v0.2.2 h1:BC7xzaVpfWIYZRNE8NhO9zo8KA4eGUL6L/JWXDh3GF0=
|
|
||||||
github.com/charmbracelet/x/ansi v0.2.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
|
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
|
|
||||||
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
|
|
||||||
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
@ -39,16 +20,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
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/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
|
||||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
github.com/gdamore/tcell/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV0=
|
github.com/gdamore/tcell/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV0=
|
||||||
github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
||||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
|
||||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
|
||||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||||
@ -59,41 +34,29 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
|
|||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
|
||||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
|
||||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
|
||||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
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/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/mattn/go-tty v0.0.5 h1:s09uXI7yDbXzzTTfw3zonKFzwGkyYlgU3OMjqA0ddz4=
|
|
||||||
github.com/mattn/go-tty v0.0.5/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28=
|
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y=
|
github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||||
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
||||||
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
|
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY=
|
||||||
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
|
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@ -102,8 +65,8 @@ github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c h1:zIYU4PjQJ4BnYryMmpyi
|
|||||||
github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c/go.mod h1:lBUy/T5kyMudFzWUH/C2moN+NlU5qF505vzOyINXuUQ=
|
github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c/go.mod h1:lBUy/T5kyMudFzWUH/C2moN+NlU5qF505vzOyINXuUQ=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
||||||
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||||
@ -120,20 +83,14 @@ github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18W
|
|||||||
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
|
||||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
|
||||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
||||||
@ -141,9 +98,8 @@ golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4
|
|||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
package invites
|
package invites
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tilde.town/tildetown/town/codes"
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dsn = "/town/var/invites/invites.db?mode=rw"
|
dsn = "/town/var/invites/invites.db?mode=rw"
|
||||||
|
codeLen = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
type Invite struct {
|
type Invite struct {
|
||||||
@ -29,7 +33,7 @@ func (i *Invite) Insert(db *sql.DB) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
i.Code = codes.NewCode(i.Email)
|
i.Code = generateCode(i.Email)
|
||||||
|
|
||||||
_, err = stmt.Exec(i.Code, i.Email)
|
_, err = stmt.Exec(i.Code, i.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -49,6 +53,44 @@ func ConnectDB() (*sql.DB, error) {
|
|||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateCode(email string) string {
|
||||||
|
|
||||||
|
charset := "abcdefghijklmnopqrztuvwxyz"
|
||||||
|
charset += strings.ToUpper(charset)
|
||||||
|
charset += "0123456789"
|
||||||
|
charset += "`~!@#$%^&*()-=_+[]{}|;:,./<>?"
|
||||||
|
|
||||||
|
code := []byte{}
|
||||||
|
|
||||||
|
max := big.NewInt(int64(len(charset)))
|
||||||
|
for len(code) < codeLen {
|
||||||
|
ix, err := rand.Int(rand.Reader, max)
|
||||||
|
if err != nil {
|
||||||
|
// TODO this is bad but I'm just kind of hoping it doesn't happen...often
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
code = append(code, charset[ix.Int64()])
|
||||||
|
}
|
||||||
|
|
||||||
|
code = append(code, ' ')
|
||||||
|
|
||||||
|
eb := []byte(email)
|
||||||
|
for x := 0; x < len(eb); x++ {
|
||||||
|
code = append(code, eb[x])
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64.StdEncoding.EncodeToString(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decode(code string) ([]string, error) {
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Split(string(decoded), " "), nil
|
||||||
|
}
|
||||||
|
|
||||||
func Get(db *sql.DB, code string) (*Invite, error) {
|
func Get(db *sql.DB, code string) (*Invite, error) {
|
||||||
inv := &Invite{
|
inv := &Invite{
|
||||||
Code: code,
|
Code: code,
|
||||||
|
44
motd
44
motd
@ -1,44 +0,0 @@
|
|||||||
_
|
|
||||||
o | | |
|
|
||||||
_|_ | | __| _ _|_ __ _ _
|
|
||||||
| | |/ / | |/ | / \_| | |_/ |/ |
|
|
||||||
|_/|_/|__/\_/|_/|__/o|_/\__/ \/ \/ | |_/ we're glad you're here!
|
|
||||||
|
|
||||||
Some commands to get you started:
|
|
||||||
+-----------------------------------------------------------------+
|
|
||||||
| nano | edit a file |
|
|
||||||
| town explore | see recent changes around the town |
|
|
||||||
| town bbj | post in our local forum |
|
|
||||||
| town feels | start a little command line blog |
|
|
||||||
| town botany | plant a little garden |
|
|
||||||
| town mail | read and send e-mail to other users |
|
|
||||||
| town wiki | edit or view the community wiki |
|
|
||||||
| town writo | draw on an infinite plane with others |
|
|
||||||
| town graffiti | add to tilde.town/graffiti.html |
|
|
||||||
| town chat | get real-time help with the town in a chatroom |
|
|
||||||
| town aup | read our code of conduct |
|
|
||||||
+-----------------------------------------------------------------+
|
|
||||||
|
|
||||||
!!!!!!!!!!!!!!!!!!!!PSA PSA PSA PSA!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
||||||
! The town is now running on physical hardware! this was a major !
|
|
||||||
! rebuild of the server and as such lots of little things are !
|
|
||||||
! probably broken. plz let an admin know about any issues. !
|
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
||||||
|
|
||||||
Survival tips:
|
|
||||||
+---------------------------------------------------------+
|
|
||||||
| screen really messed up? | hit ctrl+l |
|
|
||||||
| stuck in a program? | try ctrl+c or q |
|
|
||||||
| help on a command? | run `manual "command name"` |
|
|
||||||
| tired of reopening stuff? | look into `tmux` |
|
|
||||||
| trouble with latency? | look into `mosh` |
|
|
||||||
| forget a town command? | run `town help` |
|
|
||||||
| need help? | ask in bbj or chat |
|
|
||||||
+---------------------------------------------------------+
|
|
||||||
|
|
||||||
Stuff to do:
|
|
||||||
* Edit public_html/index.html and something beautiful
|
|
||||||
(run `nano public_html/index.html`)
|
|
||||||
* See our full list of commands by running `town`
|
|
||||||
* Create a virtual world in `town holodeck`
|
|
||||||
* Generate poetry with `prosaic`
|
|
@ -12,6 +12,7 @@ import (
|
|||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
email "git.tilde.town/tildetown/town/email"
|
email "git.tilde.town/tildetown/town/email"
|
||||||
)
|
)
|
||||||
@ -177,6 +178,7 @@ if you did _not_ request this, please let an admin know.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func genGiteaPassword() string {
|
func genGiteaPassword() string {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
b := make([]byte, 20)
|
b := make([]byte, 20)
|
||||||
for i := range b {
|
for i := range b {
|
||||||
b[i] = pwLetters[rand.Intn(len(pwLetters))]
|
b[i] = pwLetters[rand.Intn(len(pwLetters))]
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
# town share
|
|
||||||
|
|
||||||
_being a way to share multimedia with each other that mimics rot_
|
|
||||||
|
|
||||||
sometimes, townies want to share images and videos with each other.
|
|
||||||
|
|
||||||
this leads to a set of behaviors:
|
|
||||||
|
|
||||||
- using a corporate photo album soft ware to create a public link (feels antithetical to town culture)
|
|
||||||
- uploading to a photo sharing site to get a link (there aren't a ton of these left, many are shady)
|
|
||||||
- SCPing a photo to town (clumsy when you just want to share from phone, uses disk space inefficiently)
|
|
||||||
|
|
||||||
though this is not a bleeding wound of a community issue, it is one that makes me itch. a kind of mosquito bite.
|
|
||||||
|
|
||||||
i do not wish to be in the business of just hosting people's massive photos. that sounds unfun and annoying.
|
|
||||||
|
|
||||||
however, i _do_ wish to be in the business of hosting artfully lossy versions of people's photos. the business grows even tastier if those photos rot -- one pixel at a time, perhaps -- until there's nothing left. disk usage should always converge on zero. the undifferentiated smear of a brown leafed forest floor. better yet: every photo leaves a unique stain of pixels. storage compact, just enough to say "there was a thing here. here's what is left."
|
|
||||||
|
|
||||||
## the flow
|
|
||||||
|
|
||||||
1. user has a picture they want to share with a persistent, easy to copy and paste url
|
|
||||||
2. user opens a webpage, https://share.tilde.town, which has an "upload image" form at the top
|
|
||||||
3. they select a style, one of:
|
|
||||||
a. dithered
|
|
||||||
b. heavily compressed
|
|
||||||
c. 1 bit
|
|
||||||
d. ascii art
|
|
||||||
4. they choose whether or not to secretify this upload
|
|
||||||
5. the image is uploaded; the bits are stored somewhere (disk or blob storage) and its initial checksum is saved as a seed value
|
|
||||||
6. the user is redirected to their photo
|
|
||||||
|
|
||||||
meanwhile, a cron job rots the photos on disk. an array of pixel values is computed based on the original checksum: these pixels are sacred and uncorruptible. N random cells are rotted (fewer for ascii art). fully rotted images are noted as done and ignored by the cron job.
|
|
||||||
|
|
||||||
## a bugaboo
|
|
||||||
|
|
||||||
i want to think through authentication. my first thought was that this would be fully public. sure, that's an option. but it will keep me up at night.
|
|
||||||
|
|
||||||
better that townies all have a secret unique to them they can print out while on server for storage in a cookie. that cookie lets them upload. it also generates galleries for them including the uploads not marked as secretified.
|
|
||||||
|
|
||||||
thus, https://share.tilde.town/~vilmibm will have my photos flowed on out.
|
|
||||||
|
|
||||||
## cli tool
|
|
||||||
|
|
||||||
town share has a corresponding CLI tool for use on or off the town. it reads the same secret used on the web site and uploads the image to the server.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ town-share foo.jpg
|
|
||||||
flavor: 1. dithering 2. heavy compression 3. 1 bit 4. ascii art
|
|
||||||
include in web galley? Yn
|
|
||||||
|
|
||||||
file's done.
|
|
||||||
|
|
||||||
https://share.tilde.town/~vilmibm/abc123.gif
|
|
||||||
```
|
|
||||||
|
|
||||||
## future dreams
|
|
||||||
|
|
||||||
the big future dream is to handle other forms of media -- video and sound. but to start I am happy with just images.
|
|
||||||
|
|
||||||
## the questions
|
|
||||||
|
|
||||||
- what image format to use in storage? iirc the dithering tricks only work for gif so that's easy. ascii art is txt. maybe heavy compression can be jpg? 1 bit could be a bmp..?
|
|
||||||
- where to store files?
|
|
||||||
- duh -- in people's home dirs. if they want to delete an image, remove it from the directory.
|
|
||||||
- this has the side effect of letting people put arbitrary images into the directory; i think that's a feature not a bug.
|
|
||||||
- what format for image names?
|
|
||||||
- initially i had the thought they should be date sorted. but, why! fuck that, just lexigraphically sort and let the filename be a random hash of some kind. something like: 8 digits, alphanumeric, dash in the middle, extension
|
|
||||||
- what sort of secret? i can just mimic whatever i do for invite codes.
|
|
||||||
- what state is required beyond the files? i want a notion of "done" for rotting. that could be a file size. ie, rot until a file is below a certain size. since the whole point of rotting (in addition to aesthetics/philosophy/lulz) is to reduce disk impact over time, file size should be the indication of whether further rot is required.
|
|
37
share/go.mod
37
share/go.mod
@ -1,37 +0,0 @@
|
|||||||
module git.tilde.town/tildetown/share
|
|
||||||
|
|
||||||
go 1.24
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/gin-gonic/gin v1.10.0
|
|
||||||
github.com/google/uuid v1.6.0
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/bytedance/sonic v1.11.6 // indirect
|
|
||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
|
||||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
|
||||||
golang.org/x/crypto v0.23.0 // indirect
|
|
||||||
golang.org/x/net v0.25.0 // indirect
|
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
|
||||||
golang.org/x/text v0.15.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
|
91
share/go.sum
91
share/go.sum
@ -1,91 +0,0 @@
|
|||||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
|
||||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
|
||||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
|
||||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
|
||||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
|
||||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
|
||||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
|
||||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
|
||||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
|
||||||
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
|
||||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
||||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
|
||||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
|
||||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
|
||||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
|
||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
107
share/main.go
107
share/main.go
@ -1,107 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
UPLOAD_DIR = "./uploads"
|
|
||||||
MAX_FILE_SIZE = 10 << 20
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := os.MkdirAll(UPLOAD_DIR, 0755); err != nil {
|
|
||||||
panic(fmt.Sprintf("Failed to create upload directory: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
router := gin.Default()
|
|
||||||
|
|
||||||
router.LoadHTMLGlob("templates/*")
|
|
||||||
|
|
||||||
router.GET("/", homeHandler)
|
|
||||||
router.POST("/upload", uploadHandler)
|
|
||||||
|
|
||||||
router.Static("/static", "./static")
|
|
||||||
|
|
||||||
router.Run(":8787")
|
|
||||||
}
|
|
||||||
|
|
||||||
func homeHandler(c *gin.Context) {
|
|
||||||
c.HTML(http.StatusOK, "index.html", gin.H{
|
|
||||||
"title": "town share",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func uploadHandler(c *gin.Context) {
|
|
||||||
file, header, err := c.Request.FormFile("image")
|
|
||||||
if err != nil {
|
|
||||||
renderError(c, "melancholy")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
if header.Size > MAX_FILE_SIZE {
|
|
||||||
renderError(c, "max size is 10mb")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
contentType := header.Header.Get("Content-Type")
|
|
||||||
if !isValidImageType(contentType) {
|
|
||||||
renderError(c, "only jpg, png, and gif are supported")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO tweak this
|
|
||||||
extension := filepath.Ext(header.Filename)
|
|
||||||
uniqueFilename := fmt.Sprintf("%s-%s%s",
|
|
||||||
uuid.New().String(),
|
|
||||||
time.Now().Format("20060102-150405"),
|
|
||||||
extension,
|
|
||||||
)
|
|
||||||
|
|
||||||
destPath := filepath.Join(UPLOAD_DIR, uniqueFilename)
|
|
||||||
dest, err := os.Create(destPath)
|
|
||||||
if err != nil {
|
|
||||||
renderError(c, "Failed to save file")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer dest.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(dest, file); err != nil {
|
|
||||||
renderError(c, "Failed to save file")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO global const for title
|
|
||||||
c.HTML(http.StatusOK, "index.html", gin.H{
|
|
||||||
"title": "town share",
|
|
||||||
"success": fmt.Sprintf("file uploaded successfully as %s", uniqueFilename),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// renderError renders the home page with an error message
|
|
||||||
func renderError(c *gin.Context, message string) {
|
|
||||||
c.HTML(http.StatusOK, "index.html", gin.H{
|
|
||||||
"title": "town share",
|
|
||||||
"error": message,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// isValidImageType checks if the content type is a supported image format
|
|
||||||
func isValidImageType(contentType string) bool {
|
|
||||||
validTypes := map[string]bool{
|
|
||||||
"image/jpeg": true,
|
|
||||||
"image/jpg": true,
|
|
||||||
"image/png": true,
|
|
||||||
"image/gif": true,
|
|
||||||
}
|
|
||||||
return validTypes[contentType]
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{{ .title }}</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
.message {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.success {
|
|
||||||
background-color: #dff0d8;
|
|
||||||
color: #3c763d;
|
|
||||||
border: 1px solid #d6e9c6;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
background-color: #f2dede;
|
|
||||||
color: #a94442;
|
|
||||||
border: 1px solid #ebccd1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>town share</h1>
|
|
||||||
<p style="text-align: center;"></p>
|
|
||||||
|
|
||||||
<form action="/upload" method="post" id="uploadForm" class="upload-form" enctype="multipart/form-data">
|
|
||||||
<input type="file" id="imageInput" name="image" accept="image/jpeg,image/png,image/gif" required>
|
|
||||||
<p>jpg, png, gif (max 10mb)</p>
|
|
||||||
<button type="submit" class="upload-button" id="uploadButton" disabled>Upload Image</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ if .error }}
|
|
||||||
<div class="message error">{{ .error }}</div>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ if .success }}
|
|
||||||
<div class="message success">{{ .success }}</div>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
<div id="messageArea" class="message">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const fileInput = document.getElementById('imageInput');
|
|
||||||
const uploadForm = document.getElementById('uploadForm');
|
|
||||||
const uploadButton = document.getElementById('uploadButton');
|
|
||||||
const messageArea = document.getElementById('messageArea');
|
|
||||||
|
|
||||||
fileInput.addEventListener('change', (e) => {
|
|
||||||
handleFileSelection(e.target.files);
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleFileSelection(files) {
|
|
||||||
if (files.length > 0) {
|
|
||||||
const file = files[0];
|
|
||||||
if (file.type.startsWith('image/')) {
|
|
||||||
if (file.size <= 10 * 1024 * 1024) { // 10MB limit
|
|
||||||
fileInput.files = files;
|
|
||||||
uploadButton.disabled = false;
|
|
||||||
dropZone.querySelector('p').textContent = `Selected: ${file.name}`;
|
|
||||||
} else {
|
|
||||||
showMessage('File is too large. Maximum size is 10MB.', 'error');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showMessage('Please select an image file (JPG, PNG, or GIF).', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showMessage(text, type) {
|
|
||||||
messageArea.innerHTML = `<div class="message ${type}">${text}</div>`;
|
|
||||||
if (type === 'error') {
|
|
||||||
uploadButton.disabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,7 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS auth_codes (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
created TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M', 'now', 'localtime')),
|
|
||||||
code TEXT,
|
|
||||||
email TEXT,
|
|
||||||
used INTEGER DEFAULT 0
|
|
||||||
);
|
|
192
towncon.html
192
towncon.html
@ -1,192 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>~TOWN CON 2024 </title>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.5, user-scalable=yes" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<style>
|
|
||||||
@media screen and (min-width: 1000px) {
|
|
||||||
body {
|
|
||||||
margin-right: 25%;
|
|
||||||
margin-left: 25%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
background-color: pink;
|
|
||||||
max-width: 1000px;
|
|
||||||
}
|
|
||||||
h1,h2,h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
h1 { font-size: 400%; }
|
|
||||||
h2 { font-size: 200%; }
|
|
||||||
h3 { font-size: 180%; }
|
|
||||||
.callout {
|
|
||||||
font-size: 125%;
|
|
||||||
margin:1em;
|
|
||||||
}
|
|
||||||
.purp {
|
|
||||||
padding: 1em;
|
|
||||||
background-color: #E0B0FF;
|
|
||||||
}
|
|
||||||
.dark {
|
|
||||||
background-color: black;
|
|
||||||
color: #E0B0FF;
|
|
||||||
padding:1em;
|
|
||||||
}
|
|
||||||
.hero {
|
|
||||||
background-color: rgba(0,0,0,.1);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<h1>~TOWN CON 2024</h1>
|
|
||||||
<marquee>
|
|
||||||
<h2>~*~*~* october 11th & 12th *~*~*~</h2>
|
|
||||||
</marquee>
|
|
||||||
<h1><a href="schedule.html">SCHEDULE!</a></h1>
|
|
||||||
<div class="hero">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<pre title="go to a random page">/^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\
|
|
||||||
< |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| >
|
|
||||||
< | ( ) . ' t i l d e . t o w n | >
|
|
||||||
< | . | >
|
|
||||||
< | __ : | >
|
|
||||||
< | / /\ __ ___!__ _,__ ___,_ | >
|
|
||||||
< |/ / o\/ \ / /\ /__/ \ /__\__\ . . .| >
|
|
||||||
< | / \\ \/_____ /_*\ | |[^][^| | |[^]())[^| >
|
|
||||||
< |/ /\ | | |_|__| .: |__|__| ()(/) | >
|
|
||||||
< | || | {^} | | ( ~)()o| >
|
|
||||||
< | [] [] | | | |. . A ._ . .( O((/~)| >
|
|
||||||
< | _ _ |___|__|__D_| . H / \ {^} ()||) | >
|
|
||||||
< | | |[@] | | . . . | |/ \ | \|| __| >
|
|
||||||
< |_|_|_____| . . |^| \ | v ||/vv| >
|
|
||||||
< | . . v v . | | \|. v /vvv| >
|
|
||||||
< | . v v . / O \ /| . /vvvv| >
|
|
||||||
< | |_ u _| . . / _ \ / | .| | >
|
|
||||||
< | || | || / |_| \/ | | | >
|
|
||||||
< | | | |. | | >
|
|
||||||
<3|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| >
|
|
||||||
\vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv/
|
|
||||||
</pre>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<p class="callout"><em>
|
|
||||||
Being a celebration of the ten year anniversary of <a href="https://tilde.town">tilde.town</a> happening in spaces both Cyber and Meat in October of Twenty Twenty Four.
|
|
||||||
</em></p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!--
|
|
||||||
rough thoughts. for the 24 hours of october 11th i want to divide the day up into slots (how many based on what i get) for submissions. every submission gets a slot for people to see it, consume it, sit with it, chat about it. the primary mode of interaction is text chat with accompanying teleconference (bandwidth permitting) / in person room.
|
|
||||||
|
|
||||||
submissions can be anything that fit within an hour.
|
|
||||||
-->
|
|
||||||
<h3>WHAT ARE WE GETTING UP TO?</h3>
|
|
||||||
<p>
|
|
||||||
TOWN CON is a combined gallery opening and tech conference happening simultaneously in cyberspace and meatspace. Townies are encouraged to submit digital works of any kind: talks, writing, songs, poems, visual art, software. Each submitted work will get a viewing period starting at 10:00am (us central time) on October 11th. We'll all pile into a text based environment on the server to hang out together and discuss each submission.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="/~vilmibm">I</a> have reserved a <strong>cool warehouse loft</strong> in CHICAGO, ILLINOIS, USA in which townies can commune, consume, create, and sleep. I will have the space open from the evening of October 10th through the morning of October 13th. IF YOU WANT TO COME IN PERSON it's extra important to use the <code>town con</code> command to RSVP.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>October 12th is a special day</strong> for townies who come to the physical town con space. We'll be doing <em>something</em>. Music jam, walk in the woods, minigolf, cooking. TBD.
|
|
||||||
</p>
|
|
||||||
<div class="purp">
|
|
||||||
<h1>RUN THE `town con` COMMAND ON THE SERVER to learn EVEN MORE and also RSVP</h1>
|
|
||||||
</div>
|
|
||||||
<div class="dark">
|
|
||||||
<h3>TOWNIES: SUBMIT A THING!</h3>
|
|
||||||
<p>
|
|
||||||
Seriously: submit anything that can be consumed via a computer in under 60 minutes. Some ideas:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>a pre-recorded talk</li>
|
|
||||||
<li>a live talk</li>
|
|
||||||
<li>a web page</li>
|
|
||||||
<li>an essay</li>
|
|
||||||
<li>a short story</li>
|
|
||||||
<li>a poem</li>
|
|
||||||
<li>some ascii art</li>
|
|
||||||
<li>a soft ware</li>
|
|
||||||
<li>some dance choreography</li>
|
|
||||||
<li>some XML</li>
|
|
||||||
<li>a photograph of a cool stick you found</li>
|
|
||||||
<li>a picture of your cat</li>
|
|
||||||
<li>a picture of someone else's cat</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
The whole purpose of this event is to reflect and celebrate the town's vibrant and idiosyncratic community. No work is too small.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>The DEADLINE to submit is October 1st, 2024.</strong> Please use the command <code>town con</code> to submit a proposal or work. I am downright excited to talk to you about ideas and offer suggestions! Please reach out via email or come chat about it in `town chat`.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="purp">
|
|
||||||
<h3>JAMS</h3>
|
|
||||||
<p>
|
|
||||||
Town Con will be a great time to collaborate with other townies. We'll have a few concurrent jams--drop in/drop out creative sessions organized around a theme or activity. If you want to organize a jam please submit a proposal via the <code>town con</code> command.
|
|
||||||
<p>
|
|
||||||
Known jams:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>music jam (in person only)</li>
|
|
||||||
<li>88x31 badge jam</li>
|
|
||||||
<li>forum software jam</li>
|
|
||||||
<li>game jam</li>
|
|
||||||
<li>dos jam</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<h2>a sestina</h2>
|
|
||||||
<pre> Have you guess 'd you yourself would not continue?
|
|
||||||
The wanderer would not heed me
|
|
||||||
`` He told me at parting, that he should soon write
|
|
||||||
Mr. Elton looked all happiness at this proposition
|
|
||||||
what is it to us what the rest do or think?
|
|
||||||
I doubted it more the next day on Box Hill
|
|
||||||
|
|
||||||
Come thou, arise from the ground unto the place yonder
|
|
||||||
One was the son of Mars, and was killed before Troy
|
|
||||||
He had done his duty and could return to his son.
|
|
||||||
I never saw any thing so outree!
|
|
||||||
; woods, together with mountains, are on fire.
|
|
||||||
`` He told me at parting, that he should soon write
|
|
||||||
|
|
||||||
`` He told me at parting, that he should soon write
|
|
||||||
; and, abhorring the light, they fly { abroad} by night.
|
|
||||||
Can not you guess what this parcel holds?''
|
|
||||||
And these things I see suddenly, what mean they?
|
|
||||||
And you, paid to defile the People
|
|
||||||
Very strongly may be sincerely fainting.
|
|
||||||
|
|
||||||
she cried with a most open eagerness
|
|
||||||
`` He told me at parting, that he should soon write
|
|
||||||
Let me hear from you without delay
|
|
||||||
By him she was put in mind of what she might do
|
|
||||||
; but Emma 's countenance was as steady as her words.
|
|
||||||
Dear Miss Woodhouse, do advise me.''
|
|
||||||
|
|
||||||
It was certainly ‘barbara’ in the eyes of a Greek
|
|
||||||
One can not creep upon a journey
|
|
||||||
One was the son of Mars, and was killed before Troy
|
|
||||||
`` He told me at parting, that he should soon write
|
|
||||||
[ 77 and struck the guilty{ draught} from his mouth.
|
|
||||||
; The golden sun of June declines, It has not caught her eye.
|
|
||||||
|
|
||||||
Clarke translates ‘miseri senis ore,
|
|
||||||
: naught there is Save body, having property of touch.
|
|
||||||
Alas poor boy, he will never be better,
|
|
||||||
; but if he wished to do it, it might be done.
|
|
||||||
`` He told me at parting, that he should soon write
|
|
||||||
One was the son of Mars, and was killed before Troy
|
|
||||||
|
|
||||||
I knew who it came from. The virgin shrieks aloud
|
|
||||||
To feel the presence of a brave commanding officer But she was always right.
|
|
||||||
I 'd rather you would.'' This was a river of Troy
|
|
||||||
</pre>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -2,7 +2,7 @@ package towndb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
@ -143,27 +143,29 @@ func (u *TownUser) Insert(db *sql.DB) (err error) {
|
|||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserForEmail returns the user associated with an email or nil if no matching user is found
|
// TODO: really we should have like GetUser or something. but i don't want
|
||||||
func UserForEmail(db *sql.DB, address string) (*TownUser, error) {
|
// to have to populate the struct to do this operation for now ~equa
|
||||||
stmt, err := db.Prepare(`
|
func RenameUser(db *sql.DB, old_name string, new_name string) (err error) {
|
||||||
SELECT u.id, u.username FROM users u
|
stmt, err := db.Prepare(`UPDATE users SET username = ? WHERE username = ?`)
|
||||||
JOIN emails e ON e.userid = u.id
|
|
||||||
WHERE e.address = ?
|
|
||||||
`)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
row := stmt.QueryRow(address)
|
|
||||||
u := &TownUser{}
|
|
||||||
if err = row.Scan(&u.ID, &u.Username); err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return u, nil
|
result, err := stmt.Exec(new_name, old_name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rows == 0 {
|
||||||
|
return fmt.Errorf("couldn't find user")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectDB() (*sql.DB, error) {
|
func ConnectDB() (*sql.DB, error) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user