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,432 +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
|
|
||||||
Departing string
|
|
||||||
Transportation bool
|
|
||||||
Lodging bool
|
|
||||||
Allergies string
|
|
||||||
Couch 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 TOWNCON24", string(bs))
|
|
||||||
}
|
|
||||||
|
|
||||||
func rsvp(o opts) error {
|
|
||||||
var being string
|
|
||||||
var arriving string
|
|
||||||
var departing string
|
|
||||||
var transportation bool
|
|
||||||
lodging := true
|
|
||||||
var allergies string
|
|
||||||
couch := true
|
|
||||||
var freeform string
|
|
||||||
|
|
||||||
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("When (day/time) are you arriving?").
|
|
||||||
Value(&arriving),
|
|
||||||
huh.NewInput().Title("When (day/time) are you departing?").
|
|
||||||
Value(&departing),
|
|
||||||
).WithHideFunc(func() bool {
|
|
||||||
return being != "physical"
|
|
||||||
}),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().Title("Will you be staying overnight at the venue?").
|
|
||||||
Value(&lodging),
|
|
||||||
).WithHideFunc(func() bool {
|
|
||||||
return being != "physical"
|
|
||||||
}),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().Title("If the need arises are you ok sleeping on a couch?").
|
|
||||||
Value(&couch),
|
|
||||||
).WithHideFunc(func() bool {
|
|
||||||
return being != "physical" || !lodging
|
|
||||||
}),
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewInput().Title("Do you have any food allergies I should be aware of?").
|
|
||||||
Value(&allergies),
|
|
||||||
huh.NewConfirm().Title("Will you need any help getting to the venue?").
|
|
||||||
Description("I have a car and have some ability to help getting people to the venue").
|
|
||||||
Value(&transportation),
|
|
||||||
).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,
|
|
||||||
Departing: departing,
|
|
||||||
Transportation: transportation,
|
|
||||||
Lodging: lodging,
|
|
||||||
Allergies: allergies,
|
|
||||||
Couch: couch,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 TOWNCON24", 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 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 TOWNCON24", 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("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 "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,103 +0,0 @@
|
|||||||
# TOWN CON INFORMATION
|
|
||||||
|
|
||||||
TOWN CON is a celebration of tilde.town's 10 year anniversary featuring a day of talks, art, and music by townies.
|
|
||||||
|
|
||||||
TOWN CON's OFFICIAL START is now 10:00 am on October 11th in US Central Time (15:00 UTC). It ends when we run out of energy late on the 11th.
|
|
||||||
|
|
||||||
There is an additional day of TOWN CON on October 12th for townies attending TOWN CON MEAT EDITION in Chicago, Illinois, USA.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
The text mode environment will either be a HERMETICUM instance (if I finish the project in time) or just IRC.
|
|
||||||
|
|
||||||
## 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:
|
|
||||||
|
|
||||||
- 88x31 badge jam
|
|
||||||
- forum software jam
|
|
||||||
- game jam
|
|
||||||
- dos jam
|
|
||||||
|
|
||||||
## PHYSICAL EVENT
|
|
||||||
|
|
||||||
TOWN CON MEAT EDITION takes place October 11th and 12th in Chicago, Illinois, USA.
|
|
||||||
|
|
||||||
On the 11th we'll be plugged into the digital event together and on the 12th we'll do something in meatspace.
|
|
||||||
|
|
||||||
### live music jam
|
|
||||||
|
|
||||||
Meat edition guests are encouraged to bring small sound or music emitting devices. We'll set them up with recording system and do some jamming. Some synthesizers and a sampler will be provided.
|
|
||||||
|
|
||||||
### VENUE and LODGING
|
|
||||||
|
|
||||||
The venue is a converted warehouse loft located at:
|
|
||||||
|
|
||||||
```
|
|
||||||
Loft 606
|
|
||||||
2605 W Armitage Ave,
|
|
||||||
Chicago, IL 60647
|
|
||||||
```
|
|
||||||
|
|
||||||
The venue has enough sleeping space to lodge everyone who expressed interest in coming in person, however some people will need to volunteer to sleep on couches. I have the venue reserved for:
|
|
||||||
|
|
||||||
- the evening of October 10th (feel free to arrive this day!)
|
|
||||||
- all day October 11th
|
|
||||||
- all day October 12th
|
|
||||||
- the morning of October 13th (feel free to leave this day!)
|
|
||||||
|
|
||||||
The total cost to book this venue was **$8,637**. I've paid for it out of pocket and would love if townies could help chip in for the cost. As of right now (`2024-10-08`) I have received **$3,919** of chip in and will update this as I get donations. If you want to chip in:
|
|
||||||
|
|
||||||
- paypal: `nateksmith@pm.me`
|
|
||||||
- venmo: `@vilmibm`
|
|
||||||
|
|
||||||
To learn more about the venue you can visit their website: https://www.loft606.com/
|
|
||||||
|
|
||||||
### PARKING
|
|
||||||
|
|
||||||
There are 3 parking spaces provided by the venue; two of these have been claimed. If you will need parking reach out to me (or mention in your RSVP) and we'll work something out.
|
|
||||||
|
|
||||||
### FOOD
|
|
||||||
|
|
||||||
I'll arrange food throughout the event.
|
|
||||||
|
|
||||||
- 10th
|
|
||||||
- **dinner**: local restaurant food for dinner
|
|
||||||
- 11th
|
|
||||||
- breakfast: bulk order from Colectivo
|
|
||||||
- lunch: sandwich fixins
|
|
||||||
- dinner: vegan deep dish pizza from Kitchen 17
|
|
||||||
- 12th
|
|
||||||
- breakfast: bulk order from Colectivo
|
|
||||||
- lunch: sandwich fixins
|
|
||||||
- dinner: vegan mexican food from Quesadilla Del Reina Del Sur
|
|
||||||
- 13th
|
|
||||||
- breakfast: bulk order from Colectivo
|
|
||||||
|
|
||||||
There are also plenty of restaurants around the venue if people want to get their own food.
|
|
||||||
|
|
||||||
### TRANSPORTATION
|
|
||||||
|
|
||||||
The venue is accessible from rail, bus, and cab/uber/lyft. I have a car and can make myself available to pick people up and get them to the venue once they have made it to Chicago if they need. Depending on timing I might not be able to help, however. Contact via my cell phone (`~vilmibm/phone.txt`) to ask about my availability for a ride during the event.
|
|
||||||
|
|
||||||
If you are driving parking might be tough in the area--I can offer a few spots by my place in Forest Park if you want to put a car there and then get a ride over to the venue.
|
|
||||||
|
|
||||||
### ACTIVITIES
|
|
||||||
|
|
||||||
I'd like to do a field trip on the 12th...Some potential ideas:
|
|
||||||
|
|
||||||
- go to the nearby thrift store
|
|
||||||
- go out to minigolf
|
|
||||||
- go to a museum
|
|
||||||
- go into the woods
|
|
||||||
- just hang out and play synths (field trip of the mind)
|
|
||||||
|
|
||||||
!!! TBD !!!
|
|
||||||
|
|
||||||
## 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,13 +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:
|
|
||||||
|
|
||||||
- 88x31 badge jam
|
|
||||||
- forum software jam
|
|
||||||
- game jam
|
|
||||||
- dos jam
|
|
||||||
- music jam (in person only)
|
|
||||||
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
|||||||
| **user** | **type** | **description** | **central time** | **UTC** |
|
|
||||||
|--------------------|------------------|-------------------------------------------------------------------------|------------------|---------|
|
|
||||||
| curiouser | software | watch a botany plant grow (run on computer at venue) | 10:00 | 15:00 |
|
|
||||||
| vilmibm | venue talk | opening remarks | 11:00 | 16:00 |
|
|
||||||
| everyone | interactive | pick a random guestbook from /town/square/guestbooks.txt and SIGN IT | 11:30 | 16:30 |
|
|
||||||
| noelle | short story | https://noelle.dev/writing/iris/ | 12:00 | 17:00 |
|
|
||||||
| tertiaryapocalypse | browser game | https://tertiaryapocalypse.neocities.org/root-zero/ | 12:30 | 17:30 |
|
|
||||||
| audiodude | interactive poem | https://tilde.town/~audiodude/duality/ | 13:00 | 18:00 |
|
|
||||||
| login | two softwares | go play with tcoin and chin | 13:30 | 18:30 |
|
|
||||||
| vilmibm | venue talk | tour of town tech | 14:00 | 19:00 |
|
|
||||||
| grendel84 | PDF | https://tilde.town/~grendel84/solo_rpg_journal_grendel84.pdf | 14:30 | 19:30 |
|
|
||||||
| kingcons | venue talk | "Communal Computing and Inside Out Software" | 15:15 | 20:15 |
|
|
||||||
| jumblesale | photography | http://tilde.town/~jumblesale/gallery/ | 15:45 | 20:45 |
|
|
||||||
| m455 | venue talk | a talk about town | 16:00 | 21:00 |
|
|
||||||
| acdw | hypertext art | https://autocento.acdw.net | 17:00 | 22:00 |
|
|
||||||
| vilmibm | venue talk | KEYNOTE | 17:30 | 22:30 |
|
|
||||||
| codesquirrel | code | ~codesquirrel/Programs/SDL2/rawview/rawview.c | 18:00 | 23:00 |
|
|
||||||
| dozens | live talk | tilde.town awards ceremony | 18:30 | 23:30 |
|
|
||||||
| e0xb | music | https://tilde.town/~vilmibm/e0xb-town-con.flac | 19:30 | 24:30 |
|
|
||||||
| lu | art project | http://tilde.town/~lu/towncon/ | 20:00 | 01:00 |
|
|
||||||
| spinecone | venue talk | 100 years of pull request history | 20:30 | 01:30 |
|
|
||||||
| sylvie | video game | https://tilde.town/~sylvie/game.html | 21:00 | 02:00 |
|
|
||||||
| rogbeer | video of a cat | https://drive.google.com/file/d/1htP6cZZG75_arZ0tcX80v-j4NbwcukbV/view | 21:30 | 02:30 |
|
|
||||||
| pilosophos | image | https://tiny.tilde.website/@pilosophos/113275286142555603 | 21:45 | 02:45 |
|
|
||||||
| vilmibm | venue talk | closing remarks | 22:00 | 03:00 |
|
|
@ -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 999999999th, 2024.** 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.
|
|
@ -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,
|
||||||
|
38
motd
38
motd
@ -1,38 +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 |
|
|
||||||
+-----------------------------------------------------------------+
|
|
||||||
|
|
||||||
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