aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2018-09-23 14:26:02 -0700
committerChristine Dodrill <me@christine.website>2018-09-23 14:26:02 -0700
commitfe760ddbdc36008cb1747fcfb742072e5240cdd6 (patch)
tree7b6f765ac0ceb7c1078d3219746bf681f02d1c64
parent73344db07bd55329f87a4176802443edb7c464ae (diff)
downloadx-fe760ddbdc36008cb1747fcfb742072e5240cdd6.tar.xz
x-fe760ddbdc36008cb1747fcfb742072e5240cdd6.zip
x/discord: start on ilo Kesi
-rw-r--r--discord/ilo-kesi/context.go51
-rw-r--r--discord/ilo-kesi/main.go84
-rw-r--r--discord/ilo-kesi/toki_pona.go56
-rw-r--r--discord/ilo-kesi/toki_pona_test.go12
-rw-r--r--discord/ilo-kesi/within.go9
-rw-r--r--web/switchcounter/switchc.go106
6 files changed, 318 insertions, 0 deletions
diff --git a/discord/ilo-kesi/context.go b/discord/ilo-kesi/context.go
new file mode 100644
index 0000000..069bd8b
--- /dev/null
+++ b/discord/ilo-kesi/context.go
@@ -0,0 +1,51 @@
+package main
+
+import (
+ "errors"
+ "strings"
+)
+
+const (
+ actionFront = "lawa,insa"
+)
+
+var (
+ ErrUnknownAction = errors.New("ijo-kesi: unknown action")
+)
+
+type Request struct {
+ Address []*part
+ Action string
+ Subject *string // if null, user is asking for the info
+ Punct string
+}
+
+func parseRequest(inp Sentence) (*Request, error) {
+ var result Request
+
+ for _, part := range inp {
+ switch part.Part {
+ case partAddress:
+ result.Address = part.Parts
+ case partSubject:
+ if len(part.Tokens) == 1 && part.Tokens[0] != "seme" {
+ sub := strings.Join(part.Tokens, ",")
+ result.Subject = &sub
+ }
+ case partObjectMarker:
+ act := strings.Join(part.Tokens, ",")
+
+ switch act {
+ case actionFront:
+ default:
+ return nil, ErrUnknownAction
+ }
+
+ result.Action = act
+ case partPunctuation:
+ result.Punct = part.Tokens[0]
+ }
+ }
+
+ return &result, nil
+}
diff --git a/discord/ilo-kesi/main.go b/discord/ilo-kesi/main.go
new file mode 100644
index 0000000..1bc12bc
--- /dev/null
+++ b/discord/ilo-kesi/main.go
@@ -0,0 +1,84 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "net/http"
+ "time"
+
+ "github.com/Xe/x/web/switchcounter"
+ "github.com/joeshaw/envdecode"
+ _ "github.com/joho/godotenv/autoload"
+ "github.com/peterh/liner"
+)
+
+// lipuSona is the configuration.
+type lipuSona struct {
+ //DiscordToken string `env:"DISCORD_TOKEN,required"` // lipu pi lukin ala
+ TokiPonaTokenizerAPIURL string `env:"TOKI_PONA_TOKENIZER_API_URL,default=https://us-central1-golden-cove-408.cloudfunctions.net/function-1"`
+ SwitchCounterWebhook string `env:"SWITCH_COUNTER_WEBHOOK,required"`
+ IloNimi []string `env:"IJO_NIMI,default=ke;si"`
+}
+
+func main() {
+ var cfg lipuSona
+ err := envdecode.StrictDecode(&cfg)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ //pretty.Println(cfg)
+
+ sw := switchcounter.NewHTTPClient(http.DefaultClient, cfg.SwitchCounterWebhook)
+
+ line := liner.NewLiner()
+ defer line.Close()
+
+ line.SetCtrlCAborts(true)
+
+ for {
+ if inp, err := line.Prompt("|: "); err == nil {
+ if inp == "" {
+ return
+ }
+
+ line.AppendHistory(inp)
+
+ parts, err := TokenizeTokiPona(cfg.TokiPonaTokenizerAPIURL, inp)
+ if err != nil {
+ log.Printf("Can't parse: %v", err)
+ }
+
+ for _, sent := range parts {
+ req, err := parseRequest(sent)
+ if err != nil {
+ log.Printf("error: %v", err)
+ continue
+ }
+
+ switch req.Action {
+ case actionFront:
+ if req.Subject == nil {
+ st, err := sw.Status(context.Background())
+ if err != nil {
+ log.Printf("status error: %v", err)
+ continue
+ }
+
+ fmt.Printf("ilo Kesi\\ jan %s li lawa insa.\n", withinToToki[st.Front])
+
+ log.Printf("Started at: %s (%s ago)", st.StartedAt, time.Since(st.StartedAt))
+ continue
+ }
+
+ log.Printf("setting front not implemented yet :(")
+ }
+ }
+ } else if err == liner.ErrPromptAborted {
+ log.Print("Aborted")
+ } else {
+ log.Print("Error reading line: ", err)
+ }
+ }
+}
diff --git a/discord/ilo-kesi/toki_pona.go b/discord/ilo-kesi/toki_pona.go
new file mode 100644
index 0000000..9f027d2
--- /dev/null
+++ b/discord/ilo-kesi/toki_pona.go
@@ -0,0 +1,56 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "net/http"
+)
+
+type part struct {
+ Part string `json:"part"`
+ Sep *string `json:"sep"`
+ Tokens []string `json:"tokens"`
+ Parts []*part `json:"parts"`
+}
+
+const (
+ partAddress = `address`
+ partSubject = `subject`
+ partObjectMarker = `objectMarker`
+ partPrepPhrase = `prepPhrase`
+ partInterjection = `interjection`
+ partCartouche = `cartouche`
+ partPunctuation = `punctuation`
+
+ punctPeriod = `period`
+ punctQuestion = `question`
+ punctExclamation = `exclamation`
+)
+
+// A sentence is a series of sentence parts.
+type Sentence []part
+
+// TokenizeTokiPona returns a series of toki pona tokens.
+func TokenizeTokiPona(aurl, text string) ([]Sentence, error) {
+ buf := bytes.NewBuffer([]byte(text))
+ req, err := http.NewRequest(http.MethodPost, aurl, buf)
+ if err != nil {
+ return nil, err
+ }
+
+ req.Header.Add("Content-Type", "text/plain")
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var result []Sentence
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ if err != nil {
+ return nil, err
+ }
+
+ return result, nil
+}
diff --git a/discord/ilo-kesi/toki_pona_test.go b/discord/ilo-kesi/toki_pona_test.go
new file mode 100644
index 0000000..619a57f
--- /dev/null
+++ b/discord/ilo-kesi/toki_pona_test.go
@@ -0,0 +1,12 @@
+package main
+
+import (
+ "testing"
+)
+
+func TestTokenizeTokiPona(t *testing.T) {
+ _, err := TokenizeTokiPona("https://us-central1-golden-cove-408.cloudfunctions.net/function-1", "mi olin e sina.")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/discord/ilo-kesi/within.go b/discord/ilo-kesi/within.go
new file mode 100644
index 0000000..048a2de
--- /dev/null
+++ b/discord/ilo-kesi/within.go
@@ -0,0 +1,9 @@
+package main
+
+var withinToToki = map[string]string{
+ "Cadey": "Kesi",
+ "Nicole": "Liso",
+ "Jessie": "Lesi",
+ "Sephie": "Sesi",
+ "Ashe": "Ase",
+}
diff --git a/web/switchcounter/switchc.go b/web/switchcounter/switchc.go
new file mode 100644
index 0000000..d4bfbd3
--- /dev/null
+++ b/web/switchcounter/switchc.go
@@ -0,0 +1,106 @@
+// Package switchcounter is a simple interface to the https://www.switchcounter.science/ API.
+package switchcounter
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "time"
+)
+
+type arg struct {
+ Command string `json:"command"` // always "switch"
+ MemberName string `json:"member_name,omitempty"`
+}
+
+// API is the switchcounter API as an abstract interface.
+type API interface {
+ // Status returns the front of the system for this API client.
+ Status(ctx context.Context) (Status, error)
+
+ // Switch changes who is in front.
+ Switch(ctx context.Context, front string) (Status, error)
+}
+
+// Status is the API response.
+type Status struct {
+ Front string `json:"member_name"`
+ StartedAt time.Time `json:"started_at"`
+}
+
+type httpClient struct {
+ hc *http.Client
+ url string // webhook url
+}
+
+func (hc httpClient) makeRequestWith(ctx context.Context, body interface{}) (*Status, error) {
+ env := struct {
+ Webhook interface{} `json:"webhook"`
+ }{
+ Webhook: body,
+ }
+ data, err := json.Marshal(env)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("POST", hc.url, bytes.NewBuffer(data))
+ if err != nil {
+ return nil, err
+ }
+ req = req.WithContext(ctx)
+ req.Header.Add("Content-Type", "application/json")
+
+ resp, err := hc.hc.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ data, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ log.Printf("body: %s", string(data))
+
+ return nil, fmt.Errorf("http response code %d", resp.StatusCode)
+ }
+
+ var result Status
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ if err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+func (hc httpClient) Status(ctx context.Context) (Status, error) {
+ result, err := hc.makeRequestWith(ctx, arg{Command: "switch"})
+ if err != nil {
+ return Status{}, err
+ }
+ return *result, nil
+}
+
+func (hc httpClient) Switch(ctx context.Context, front string) (Status, error) {
+ result, err := hc.makeRequestWith(ctx, arg{Command: "switch", MemberName: front})
+ if err != nil {
+ return Status{}, err
+ }
+ return *result, nil
+}
+
+// NewHTTPClient creates a new instance of API over HTTP.
+func NewHTTPClient(hc *http.Client, webhookURL string) API {
+ return httpClient{
+ hc: hc,
+ url: webhookURL,
+ }
+}