-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'up-amygdala' into merge-amygdala
This initial merge is incomplete, but compiles. Next step will be to merge each main into leatherman as a proper subcommand.
- Loading branch information
Showing
26 changed files
with
1,377 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
language: go | ||
|
||
go: | ||
- 1.13.x | ||
script: | ||
- go test ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
web: amygdala -port $PORT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
# Amygdala | ||
|
||
Computer asisted brain stem. | ||
|
||
This project is comprised of a handful of tools that allow me to automate common | ||
tasks. The main tool (`amygdala`) is currently deployed to Heroku, but it could | ||
be deployed anywhere. This isn't written to run for more than one user or to | ||
meet any needs but my own. It receives input from Twilio so I can just text the | ||
thing and get either a response or have it do what I asked. | ||
|
||
## Commands | ||
|
||
### cmd | ||
|
||
Explains all the commands online. | ||
|
||
### todo | ||
|
||
This is the most generically useful of the tools. Any string that doesn't match | ||
anything else is assumed to be a todo. Simply creates a document tagged `inbox` | ||
in my Dropbox. Any attachment included in the message is linked to; if the | ||
attachment is an image it is linked to with an `img` tag. | ||
|
||
### `defer til` | ||
|
||
A command matching the following general pattern "defer til 2019-01-01" or | ||
"defer some message til 2019-01-01" will enqueue messages to be | ||
[undefered](https://github.com/frioux/leatherman#undefer) later. | ||
|
||
### `inspire me` | ||
|
||
The string `inspire me` will repy with [a random inspirational | ||
link](https://frioux.github.io/notes/posts/inspiration/). | ||
|
||
### `remind me` | ||
|
||
Remind me commands are created with an sms to amygdala of the form: | ||
|
||
* remind me to power name at 3pm | ||
* remind me to wake up at 3:05pm | ||
* remind me to make dinner in 1h | ||
* remind me to get dessert in 1h30m | ||
|
||
Files are created in a directory in Dropbox and are the acted upon by | ||
`enqueue-at` | ||
|
||
## Configuration | ||
|
||
All configuration is done via environment variables. | ||
|
||
### `DROPBOX_ACCESS_TOKEN` | ||
|
||
Used by `amygdala` to access Dropbox. | ||
|
||
### `MY_CELL` | ||
|
||
Used by `amygdala` to limit access to my own phone. | ||
|
||
### Pushover | ||
|
||
All of these are used by `wuphf` to send notifications to my phone: | ||
|
||
* `PUSHOVER_TOKEN` | ||
* `PUSHOVER_USER` | ||
* `PUSHOVER_DEVICE` | ||
|
||
### `TWILIO_AUTH_TOKEN` | ||
|
||
Used by `amygdala` to validate that requests actually came from Twilio. | ||
|
||
### `TWILIO_URL` | ||
|
||
Also used by `amygdala` to validate that requests actually came from Twilio. In | ||
theory this could be inferred from the request, but due to http proxies it must | ||
be configured instead. | ||
|
||
## Design | ||
|
||
As mentioned before, this project is a few distinct tools. Simplicity is a | ||
priority for all of them, but I've tried to keep things relatively neat. | ||
|
||
### `amygdala` | ||
|
||
The top level tool, which initiates all the other stuff, is currently organized | ||
around my personal notes system, which is not yet documented other than [this | ||
blog | ||
post](https://blog.afoolishmanifesto.com/posts/a-love-letter-to-plain-text/). | ||
With that in mind it uses an ordered, regexp based dispatcher in the | ||
`internal/notes` package. | ||
|
||
My notes are structured text in Dropbox, so the `internal/notes` package talks | ||
directly to Dropbox to download and upload files. | ||
|
||
### `enqueue-at` | ||
|
||
This runs on my laptop. If I find that I use it often I want to figure out how | ||
to run this in the cloud (without a VM.) | ||
|
||
I run it like this: | ||
|
||
```bash | ||
$ minotaur ~/Dropbox/notes/.alerts -- bin/enqueue-at | ||
``` | ||
|
||
[`minotaur` comes with my | ||
Leatherman](https://github.com/frioux/leatherman#minotaur). | ||
|
||
`enqueue-at` simply enqueues notifications via `atd`, using a tool called | ||
`wuphf`, which is included with this project. | ||
|
||
[See docs for `remind me`](#remind-me) for more details. | ||
|
||
### `wuphf` | ||
|
||
Currently this tool sends alerts via both `wall` and | ||
[pushover](https://pushover.net). All arguments are concatenated to produce the | ||
sent message. | ||
|
||
## Development | ||
|
||
Here's a list of places to look when you need to add functionality: | ||
|
||
* [NewRules](https://godoc.org/github.com/frioux/amygdala/internal/notes#NewRules) | ||
is where you can create a new command. | ||
* [Dropbox package](https://godoc.org/github.com/frioux/amygdala/internal/dropbox) | ||
* [Twilio package](https://godoc.org/github.com/frioux/amygdala/internal/twilio) | ||
|
||
It's always wise to run `go test ./...` before pushing any changes. After | ||
that, using `cmd/brain-stem` to verify changes without needing the full stack | ||
is useful. If you want more, you can run the main app (`amygdala`) and poke at | ||
it with `cmd/twilio`. There's no dropbox mock yet so you'll need to actually | ||
use the real dropbox token, but other than that you should be able to develop | ||
almost 100% locally. | ||
|
||
## History | ||
|
||
This is meant to be a simpler replacement to [my lizard | ||
brain](https://github.com/frioux/Lizard-Brain), both because the world has | ||
improved and so have I. | ||
|
||
The old system's design was around allowing arbitrary inputs and outputs; the | ||
new system is only decoupled as I see fit, instead of enforcing that arbitrarily | ||
from the start. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
#!/bin/sh | ||
|
||
# exit early if there are no CREATEd files | ||
perl -e 'exit 1 unless scalar grep m/^CREATE/, @ARGV' "$@" || exit | ||
|
||
cd ~/Dropbox/notes/.alerts | ||
|
||
for file in *; do | ||
ts="$(echo "$file" | cut -f1 -d_)" | ||
contents=$(cat "$file") | ||
|
||
# if the ts is before now | ||
if perl -e'exit 1 if shift gt shift' "$ts" "$(date -Iseconds)"; then | ||
wuphf "$contents" | ||
else | ||
echo "wuphf $contents" | at "$(date -d "$ts" '+%H:%M %Y-%m-%d')" | ||
fi | ||
rm "$file" | ||
done |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package log | ||
|
||
import ( | ||
"encoding/json" | ||
"os" | ||
"time" | ||
) | ||
|
||
type errline struct { | ||
Time string | ||
Type string | ||
|
||
Message string | ||
} | ||
|
||
var e = json.NewEncoder(os.Stdout) | ||
|
||
func Err(err error) { | ||
e.Encode(errline{ | ||
Time: time.Now().Format(time.RFC3339Nano), | ||
Type: "error", | ||
Message: err.Error(), | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package middleware | ||
|
||
import ( | ||
"io" | ||
"net/http" | ||
"time" | ||
|
||
"encoding/json" | ||
) | ||
|
||
type Adapter func(http.Handler) http.Handler | ||
|
||
func Adapt(h http.Handler, adapters ...Adapter) http.Handler { | ||
for _, adapter := range adapters { | ||
h = adapter(h) | ||
} | ||
return h | ||
} | ||
|
||
type logline struct { | ||
Time string | ||
Type string | ||
|
||
Duration float64 | ||
Method string | ||
URL string | ||
UserAgent string | ||
Proto string | ||
Host string | ||
RemoteAddr string | ||
StatusCode int | ||
} | ||
|
||
func Log(logger io.Writer) Adapter { | ||
e := json.NewEncoder(logger) | ||
|
||
return func(h http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
start := time.Now() | ||
|
||
lw := &loggingResponseWriter{ResponseWriter: w} | ||
defer func() { | ||
e.Encode(logline{ | ||
Type: "accesslog", | ||
Time: start.Format(time.RFC3339Nano), | ||
Duration: time.Now().Sub(start).Seconds(), | ||
Method: r.Method, | ||
URL: r.URL.String(), | ||
UserAgent: r.UserAgent(), | ||
Proto: r.Proto, | ||
Host: r.Host, | ||
RemoteAddr: r.RemoteAddr, | ||
StatusCode: lw.statusCode, | ||
}) | ||
}() | ||
h.ServeHTTP(lw, r) | ||
|
||
}) | ||
} | ||
} | ||
|
||
type loggingResponseWriter struct { | ||
http.ResponseWriter | ||
statusCode int | ||
} | ||
|
||
func (lrw *loggingResponseWriter) WriteHeader(code int) { | ||
lrw.statusCode = code | ||
lrw.ResponseWriter.WriteHeader(code) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package middleware | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/frioux/leatherman/internal/testutil" | ||
) | ||
|
||
func TestLog(t *testing.T) { | ||
req, err := http.NewRequest("GET", "/", nil) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
buf := new(bytes.Buffer) | ||
var inner http.HandlerFunc = func(w http.ResponseWriter, _ *http.Request) { | ||
w.WriteHeader(404) | ||
} | ||
rr := httptest.NewRecorder() | ||
handler := Adapt(inner, Log(buf)) | ||
|
||
handler.ServeHTTP(rr, req) | ||
|
||
if status := rr.Code; status != http.StatusNotFound { | ||
t.Errorf("handler returned wrong status code: got %v want %v", | ||
status, http.StatusOK) | ||
} | ||
|
||
d := json.NewDecoder(buf) | ||
var x struct{ StatusCode int } | ||
if err = d.Decode(&x); err != nil { | ||
panic(err) | ||
} | ||
|
||
testutil.Equal(t, x.StatusCode, http.StatusNotFound, "status code recorded") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package notes | ||
|
||
import ( | ||
"bufio" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"math/rand" | ||
"regexp" | ||
|
||
"github.com/frioux/leatherman/internal/dropbox" | ||
"github.com/frioux/leatherman/internal/personality" | ||
"github.com/frioux/leatherman/internal/twilio" | ||
) | ||
|
||
var isItem = regexp.MustCompile(`^\s?\*\s+(.*?)\s*$`) | ||
var mdLink = regexp.MustCompile(`^\[(.*)\]\((.*)\)$`) | ||
|
||
var errNone = errors.New("never found anything") | ||
|
||
func beerMe(r io.Reader) (string, error) { | ||
s := bufio.NewScanner(r) | ||
|
||
o := []string{} | ||
for s.Scan() { | ||
m := isItem.FindStringSubmatch(s.Text()) | ||
if len(m) != 2 { | ||
continue | ||
} | ||
o = append(o, m[1]) | ||
} | ||
|
||
if len(o) == 0 { | ||
return "", errNone | ||
} | ||
|
||
rand.Shuffle(len(o), func(i, j int) { o[i], o[j] = o[j], o[i] }) | ||
|
||
fmt.Println(mdLink.FindStringSubmatch(o[0])) | ||
if l := mdLink.FindStringSubmatch(o[0]); len(l) == 3 { | ||
return fmt.Sprintf("[%s]( %s )", l[1], l[2]), nil | ||
} | ||
|
||
return o[0], nil | ||
} | ||
|
||
func inspireMe(cl dropbox.Client) func(_ string, _ []twilio.Media) (string, error) { | ||
return func(_ string, _ []twilio.Media) (string, error) { | ||
r, err := cl.Download("/notes/content/posts/inspiration.md") | ||
if err != nil { | ||
return personality.Err(), fmt.Errorf("dropbox.Download: %w", err) | ||
} | ||
n, err := beerMe(r) | ||
if err != nil { | ||
return personality.Err(), err | ||
} | ||
return n, nil | ||
} | ||
} |
Oops, something went wrong.