aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2024-09-07 14:23:06 -0400
committerXe Iaso <me@xeiaso.net>2024-09-07 14:23:06 -0400
commit6ff73144b5555aa2852d05ae05e377cbdddf6569 (patch)
tree7b10bbc0c0df8f40068772dbd57668a5edbe65f6 /cmd
parent65c9fe6433594ba22f8300d5f4873d22ea37b37d (diff)
downloadx-6ff73144b5555aa2852d05ae05e377cbdddf6569.tar.xz
x-6ff73144b5555aa2852d05ae05e377cbdddf6569.zip
cmd: migrate aura
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/aerial/derpi/derpi.go87
-rw-r--r--cmd/aerial/hipster.go50
-rw-r--r--cmd/aerial/main.go11
-rw-r--r--cmd/aerial/pvfm.go81
-rw-r--r--cmd/aura/aura.templ15
-rw-r--r--cmd/aura/aura.webpbin0 -> 10554 bytes
-rw-r--r--cmd/aura/aura_templ.go63
-rw-r--r--cmd/aura/main.go396
-rw-r--r--cmd/aura/pvfm.go1
-rw-r--r--cmd/aura/var/.gitignore2
10 files changed, 480 insertions, 226 deletions
diff --git a/cmd/aerial/derpi/derpi.go b/cmd/aerial/derpi/derpi.go
deleted file mode 100644
index eab910a..0000000
--- a/cmd/aerial/derpi/derpi.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package derpi
-
-import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "strings"
- "time"
-)
-
-// DerpiResults is a struct to contain Derpibooru search results
-type DerpiResults struct {
- Search []struct {
- ID string `json:"id"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
- DuplicateReports []interface{} `json:"duplicate_reports"`
- FirstSeenAt time.Time `json:"first_seen_at"`
- UploaderID string `json:"uploader_id"`
- Score int `json:"score"`
- CommentCount int `json:"comment_count"`
- Width int `json:"width"`
- Height int `json:"height"`
- FileName string `json:"file_name"`
- Description string `json:"description"`
- Uploader string `json:"uploader"`
- Image string `json:"image"`
- Upvotes int `json:"upvotes"`
- Downvotes int `json:"downvotes"`
- Faves int `json:"faves"`
- Tags string `json:"tags"`
- TagIds []string `json:"tag_ids"`
- AspectRatio float64 `json:"aspect_ratio"`
- OriginalFormat string `json:"original_format"`
- MimeType string `json:"mime_type"`
- Sha512Hash string `json:"sha512_hash"`
- OrigSha512Hash string `json:"orig_sha512_hash"`
- SourceURL string `json:"source_url"`
- Representations struct {
- ThumbTiny string `json:"thumb_tiny"`
- ThumbSmall string `json:"thumb_small"`
- Thumb string `json:"thumb"`
- Small string `json:"small"`
- Medium string `json:"medium"`
- Large string `json:"large"`
- Tall string `json:"tall"`
- Full string `json:"full"`
- } `json:"representations"`
- IsRendered bool `json:"is_rendered"`
- IsOptimized bool `json:"is_optimized"`
- } `json:"search"`
- Total int `json:"total"`
- Interactions []interface{} `json:"interactions"`
-}
-
-// Perform a Derpibooru search query with a given string of tags and an API key
-func SearchDerpi(tags string) (DerpiResults, error) {
-
- // format for URL query
- tags += ",safe" // Enforce the safe tag for PG rating
- derpiTags := strings.Replace(tags, " ", "+", -1)
-
- // make URL query
- urlQuery := "https://derpibooru.org/search.json?q=" + derpiTags
- resp, err := http.Get(urlQuery)
- if err != nil {
- return DerpiResults{}, fmt.Errorf("Failed with HTTP error.")
- }
-
- // read response body
- defer resp.Body.Close()
- respBody, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return DerpiResults{}, fmt.Errorf("Failed with error reading response body.")
- }
-
- // parse json
- results := DerpiResults{}
- err = json.Unmarshal(respBody, &results)
- if err != nil {
- return DerpiResults{}, fmt.Errorf("Failed with JSON parsing error.")
- }
-
- return results, nil
-
-}
diff --git a/cmd/aerial/hipster.go b/cmd/aerial/hipster.go
deleted file mode 100644
index e6728c5..0000000
--- a/cmd/aerial/hipster.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "io/ioutil"
- "net/http"
- "strings"
-
- "github.com/bwmarrin/discordgo"
-)
-
-func hipster(s *discordgo.Session, m *discordgo.Message, parv []string) error {
- msg, err := getHipsterText()
- if err != nil {
- return err
- }
-
- s.ChannelMessageSend(m.ChannelID, msg)
- return nil
-}
-
-func getHipsterText() (string, error) {
- resp, err := http.Get("http://hipsterjesus.com/api/?type=hipster-centric&html=false") // paras parameter no longer respected, need to re-implement locally
- if err != nil {
- return "", err
- }
-
- textStruct := &struct {
- Text string `json:"text"`
- }{}
-
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return "", err
- }
-
- json.Unmarshal(body, textStruct)
-
- text := strings.Split(textStruct.Text, ". ")[0]
- textSlice := strings.Split(text, " ") // Separate each word into an array
- truncatedText := ""
-
- wordCount := 5 // change this to adjust word count
-
- for i := 0; i < wordCount; i++ {
- truncatedText += textSlice[i] + " "
- }
-
- return truncatedText, nil
-}
diff --git a/cmd/aerial/main.go b/cmd/aerial/main.go
index e62e977..5cdeb24 100644
--- a/cmd/aerial/main.go
+++ b/cmd/aerial/main.go
@@ -19,12 +19,6 @@ type aerial struct {
s *discordgo.Session
}
-const (
- djonHelp = ``
- djoffHelp = ``
- setupHelp = ``
-)
-
func (a *aerial) Handle(s *discordgo.Session, m *discordgo.MessageCreate) {
err := a.cs.Run(s, m.Message)
if err != nil {
@@ -35,8 +29,6 @@ func (a *aerial) Handle(s *discordgo.Session, m *discordgo.MessageCreate) {
var (
token = os.Getenv("TOKEN")
youtubeSpamRoomID = os.Getenv("DISCORD_YOUTUBESPAM_ROOMID")
- gClientID = os.Getenv("GOOGLE_CLIENT_ID")
- gClientSecret = os.Getenv("GOOGLE_CLIENT_SECRET")
musicLinkRegex = regexp.MustCompile(`(.*)((http(s?):\/\/(www\.)?soundcloud.com\/.*)|(http(s?):\/\/(www\.)?youtube.com\/.*)|(http(s?):\/\/(www\.)?youtu.be\/.*))(.*)|(.*)http(s?):\/\/(www\.)?mixcloud.com\/.*`)
)
@@ -58,13 +50,10 @@ func main() {
a.cs.AddCmd("stats", "shows radio station statistics for Ponyville FM", bot.NoPermissions, stats)
a.cs.AddCmd("dj", "shows which DJ is up next on Ponyville FM", bot.NoPermissions, stats)
a.cs.AddCmd("schedule", "shows the future radio schedule for Ponyville FM", bot.NoPermissions, schedule)
- a.cs.AddCmd("hipster", "hip me up fam", bot.NoPermissions, hipster)
a.cs.AddCmd("source", "source code information", bot.NoPermissions, source.Source)
a.cs.AddCmd("time", "shows the current bot time", bot.NoPermissions, curTime)
a.cs.AddCmd("streams", "shows the different Ponyville FM stream links", bot.NoPermissions, streams)
a.cs.AddCmd("servers", "shows the different Ponyville FM stream links", bot.NoPermissions, streams)
- a.cs.AddCmd("derpi", "grabs a random **__safe__** image from Derpibooru with the given search results", bot.NoPermissions, derpi)
- a.cs.AddCmd("weather", "how's the weather right now?", bot.NoPermissions, weather)
dg.AddHandler(a.Handle)
dg.AddHandler(pesterLink)
diff --git a/cmd/aerial/pvfm.go b/cmd/aerial/pvfm.go
index 97f13bb..cb6cb69 100644
--- a/cmd/aerial/pvfm.go
+++ b/cmd/aerial/pvfm.go
@@ -3,27 +3,16 @@ package main
import (
"fmt"
"log"
- "math/rand"
"strconv"
"strings"
"time"
"github.com/bwmarrin/discordgo"
- derpiSearch "within.website/x/cmd/aerial/derpi"
"within.website/x/internal/pvfm"
pvfmschedule "within.website/x/internal/pvfm/schedule"
"within.website/x/internal/pvfm/station"
)
-func init() {
- rand.Seed(time.Now().Unix())
-}
-
-// randomRange gives a random whole integer between the given integers [min, max)
-func randomRange(min, max int) int {
- return rand.Intn(max-min) + min
-}
-
func pesterLink(s *discordgo.Session, m *discordgo.MessageCreate) {
if musicLinkRegex.Match([]byte(m.Content)) {
i, err := pvfm.GetStats()
@@ -169,6 +158,8 @@ PonyvilleFM Europe OGG Stream:
http://dj.bronyradio.com:8000/pvfm1.ogg
PonyvilleFM Europe Stream:
http://dj.bronyradio.com:8000/stream.mp3
+PonyvilleFM 2 Stream:
+http://luna.ponyvillefm.com/listen/pvfm2/radio.mp3
PonyvilleFM Free MP3 24/7 Pony Stream:
http://dj.bronyradio.com:8000/pvfmfree.mp3
PonyvilleFM Free OGG 24/7 Pony Stream:
@@ -185,76 +176,10 @@ func streams(s *discordgo.Session, m *discordgo.Message, parv []string) error {
// PVFM
outputEmbed.AddField(":musical_note: PVFM Servers", pvfmList)
// Luna Radio
- outputEmbed.AddField(":musical_note: Luna Radio Servers", "Luna Radio MP3 128Kbps Stream:\n<http://radio.ponyvillelive.com:8002/stream.mp3>\nLuna Radio Mobile MP3 64Kbps Stream:\n<http://radio.ponyvillelive.com:8002/mobile?;stream.mp3>\n")
+ outputEmbed.AddField(":musical_note: Luna Radio Servers", "Luna Radio MP3 128Kbps Stream:\n<http://luna.ponyvillefm.com/listen/lunaradio/radio.mp3>\n")
// Recordings
outputEmbed.AddField(":cd: DJ Recordings", "Archive\n<https://pvfm.within.lgbt/var/93252527679639552/>\nLegacy Archive\n<https://pvfm.within.lgbt/BronyRadio/>")
s.ChannelMessageSendEmbed(m.ChannelID, outputEmbed.MessageEmbed)
-
- // no errors yay!!!!
- return nil
-}
-
-func derpi(s *discordgo.Session, m *discordgo.Message, parv []string) error {
- if m.ChannelID == "292755043684450304" {
-
- searchResults, err := derpiSearch.SearchDerpi(m.Content[7:len(m.Content)]) // Safe tag will be added in derpi/derpi.go
- if err != nil {
- s.ChannelMessageSend(m.ChannelID, "An error occured.")
- return err
- }
- if len(searchResults.Search) < 1 {
- s.ChannelMessageSend(m.ChannelID, "Error: No results")
- return nil
- }
- derpiImage := searchResults.Search[randomRange(0, len(searchResults.Search))]
-
- tags := strings.Split(derpiImage.Tags, ", ") // because this isn't an array for some reason
-
- // Check for artist tag
- artist := ""
- for _, tag := range tags {
- if strings.Contains(tag, "artist:") {
- artist = tag[7:]
- }
- }
-
- outputEmbed := NewEmbed().
- SetTitle("Derpibooru Image").
- SetURL("https://derpibooru.org/" + derpiImage.ID).
- SetDescription(derpiImage.Description).
- SetImage("http:" + derpiImage.Image).
- SetFooter("Image score: " + strconv.Itoa(derpiImage.Score) + " | Uploaded: " + derpiImage.CreatedAt.String())
-
- // Credit the artist!
- if artist == "" {
- outputEmbed.SetAuthor("No artist")
- } else {
- outputEmbed.SetAuthor("Artist: " + artist)
- }
-
- s.ChannelMessageSendEmbed(m.ChannelID, outputEmbed.MessageEmbed)
- } else {
- s.ChannelMessageSend(m.ChannelID, "Please use this command in <#292755043684450304> only.")
- }
- return nil
-}
-
-func weather(s *discordgo.Session, m *discordgo.Message, parv []string) error {
- responses := []string{
- "Cloudy with a chance of meatballs.",
- "It's currently pouring down even more than Pinkie.",
- "It's the most overcast I've ever seen. In other words, same as always.",
- "Do you have a better conversation starter than that?",
- "There's at least 5 or 6 weather right now, my dude.",
- "It's soggy enough for Rainbow Dash to get fired, if she didn't have a literal deity keeping her in charge.",
- "Surprisingly, the weather is pretty alright.",
- "You'd be happy to know that it's hot enough to make a phoenix sweat.",
- "The weather right now is like you took London and stuck it in a dishwasher.",
- "The Crystal Empire is warmer than this weather.",
- }
-
- s.ChannelMessageSend(m.ChannelID, responses[randomRange(0, len(responses))])
-
return nil
}
diff --git a/cmd/aura/aura.templ b/cmd/aura/aura.templ
new file mode 100644
index 0000000..2d55b88
--- /dev/null
+++ b/cmd/aura/aura.templ
@@ -0,0 +1,15 @@
+package main
+
+templ index() {
+ <p><img src="/static/aura.webp"/></p>
+ <h2>Archive</h2>
+ <ul>
+ <li><a href="/sleepypony/">Early DJ Sleepypony sets</a></li>
+ <li><a href="/BronyRadio/">Early BronyRadio sets</a></li>
+ <li><a href="/var/93252527679639552/">Current PonyvilleFM sets</a></li>
+ </ul>
+}
+
+templ notFound() {
+ <p>The URL you requested could not be found. Please check your URL and hang up to try your call again.</p>
+} \ No newline at end of file
diff --git a/cmd/aura/aura.webp b/cmd/aura/aura.webp
new file mode 100644
index 0000000..77d6102
--- /dev/null
+++ b/cmd/aura/aura.webp
Binary files differ
diff --git a/cmd/aura/aura_templ.go b/cmd/aura/aura_templ.go
new file mode 100644
index 0000000..421d54b
--- /dev/null
+++ b/cmd/aura/aura_templ.go
@@ -0,0 +1,63 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.771
+package main
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+func index() templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p><img src=\"/static/aura.webp\"></p><h2>Archive</h2><ul><li><a href=\"/sleepypony/\">Early DJ Sleepypony sets</a></li><li><a href=\"/BronyRadio/\">Early BronyRadio sets</a></li><li><a href=\"/var/93252527679639552/\">Current PonyvilleFM sets</a></li></ul>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func notFound() templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var2 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var2 == nil {
+ templ_7745c5c3_Var2 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>The URL you requested could not be found. Please check your URL and hang up to try your call again.</p>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/cmd/aura/main.go b/cmd/aura/main.go
new file mode 100644
index 0000000..4f7a535
--- /dev/null
+++ b/cmd/aura/main.go
@@ -0,0 +1,396 @@
+package main
+
+import (
+ "embed"
+ "encoding/json"
+ "errors"
+ "flag"
+ "fmt"
+ "log"
+ "log/slog"
+ "math/rand"
+ "net/http"
+ "net/url"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/a-h/templ"
+ "github.com/bwmarrin/discordgo"
+ hashids "github.com/speps/go-hashids"
+ "within.website/x/internal"
+ "within.website/x/internal/pvfm/bot"
+ "within.website/x/internal/pvfm/commands/source"
+ "within.website/x/internal/pvfm/recording"
+ "within.website/x/xess"
+)
+
+//go:generate go run github.com/a-h/templ/cmd/templ@latest generate
+
+var (
+ token = flag.String("token", "", "Token for authentication")
+ dataPrefix = flag.String("data-prefix", "", "Data prefix")
+ recordingDomain = flag.String("recording-domain", "", "Recording domain")
+ hashidsSalt = flag.String("hashids-salt", "", "Salt for Hashids")
+ port = flag.String("port", "8080", "Port number")
+
+ //go:embed aura.webp
+ static embed.FS
+)
+
+func main() {
+ internal.HandleStartup()
+
+ dg, err := discordgo.New("Bot " + *token)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ hid := hashids.NewData()
+ hid.Salt = *hashidsSalt
+
+ hiid, err := hashids.NewWithData(hid)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ a := &aura{
+ cs: bot.NewCommandSet(),
+ s: dg,
+ guildRecordings: map[string]*rec{},
+
+ hid: hiid,
+
+ state: &state{
+ DownloadURLs: map[string]string{},
+ PermRoles: map[string]string{},
+ Shorturls: map[string]string{},
+ },
+ }
+
+ err = a.state.Load()
+ if err != nil {
+ log.Println(err)
+ }
+
+ a.cs.AddCmd("roles", "", bot.NoPermissions, a.roles)
+ a.cs.AddCmd("setup", setupHelp, bot.NoPermissions, a.setup)
+ a.cs.AddCmd("djon", djonHelp, a.Permissons, a.djon)
+ a.cs.AddCmd("djoff", djoffHelp, a.Permissons, a.djoff)
+ a.cs.AddCmd("source", "Source code information", bot.NoPermissions, source.Source)
+
+ dg.AddHandler(a.Handle)
+
+ err = dg.Open()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ log.Println("ready")
+
+ mux := http.NewServeMux()
+
+ mux.Handle("/id/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ id := r.PathValue("id")
+
+ redir, ok := a.state.Shorturls[id]
+ if !ok {
+ http.Error(w, "not found, sorry", http.StatusNotFound)
+ return
+ }
+
+ http.Redirect(w, r, redir, http.StatusFound)
+ }))
+
+ xess.Mount(mux)
+
+ mux.HandleFunc("/links.json", func(w http.ResponseWriter, r *http.Request) {
+ json.NewEncoder(w).Encode(a.state.Shorturls)
+ })
+
+ mux.Handle("/BronyRadio/", http.FileServer(http.Dir("/data")))
+ mux.Handle("/sleepypony/", http.FileServer(http.Dir("/data")))
+ mux.Handle("/toastbeard/", http.FileServer(http.Dir("/data")))
+ mux.Handle("/var/", http.FileServer(http.Dir("/data")))
+ mux.Handle("/static/", http.StripPrefix("/static/", http.FileServerFS(static)))
+ mux.Handle("/{$}", templ.Handler(
+ xess.Base(
+ "PonyvilleFM DJ recording archives",
+ nil,
+ nil,
+ index(),
+ nil,
+ ),
+ ))
+ mux.Handle("/", templ.Handler(
+ xess.Simple("Not found", notFound()),
+ templ.WithStatus(http.StatusNotFound),
+ ))
+
+ slog.Info("listening on", "url", fmt.Sprintf("http://localhost:%s", *port))
+ log.Fatal(http.ListenAndServe(":"+*port, mux))
+}
+
+func genFname(username string) (string, error) {
+ return fmt.Sprintf("%s - %s.mp3", username, time.Now().Format(time.RFC3339)), nil
+}
+
+type aura struct {
+ cs *bot.CommandSet
+ s *discordgo.Session
+
+ guildRecordings map[string]*rec
+ state *state
+ hid *hashids.HashID
+}
+
+type state struct {
+ DownloadURLs map[string]string // Guild ID -> URL
+ PermRoles map[string]string // Guild ID -> needed role ID
+ Shorturls map[string]string // hashid -> partial route
+}
+
+func (s *state) Save() error {
+ fout, err := os.Create(path.Join(*dataPrefix, "state.json"))
+ if err != nil {
+ return err
+ }
+ defer fout.Close()
+
+ return json.NewEncoder(fout).Encode(s)
+}
+
+func (s *state) Load() error {
+ fin, err := os.Open(path.Join(*dataPrefix, "state.json"))
+ if err != nil {
+ return err
+ }
+ defer fin.Close()
+
+ return json.NewDecoder(fin).Decode(s)
+}
+
+type rec struct {
+ *recording.Recording
+ creator string
+}
+
+const (
+ djonHelp = `Start a DJ set recording`
+ djoffHelp = `Stop a DJ set recording`
+ setupHelp = `Set up the bot for your guild`
+)
+
+func (a *aura) Permissons(s *discordgo.Session, m *discordgo.Message, parv []string) error {
+ ch, err := s.Channel(m.ChannelID)
+ if err != nil {
+ return err
+ }
+
+ gid := ch.GuildID
+ role := a.state.PermRoles[gid]
+
+ gu, err := s.GuildMember(gid, m.Author.ID)
+ if err != nil {
+ return err
+ }
+
+ slog.Info("want role", "role", role, "author_roles", gu.Roles)
+
+ found := false
+ for _, r := range gu.Roles {
+ if r == role {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ return errors.New("aura: no permissions")
+ }
+
+ return nil
+}
+
+func (a *aura) roles(s *discordgo.Session, m *discordgo.Message, parv []string) error {
+ log.Println("got here")
+ ch, err := s.Channel(m.ChannelID)
+ if err != nil {
+ return err
+ }
+
+ gid := ch.GuildID
+
+ result := "Roles in this group:\n"
+
+ roles, err := s.GuildRoles(gid)
+ if err != nil {
+ return err
+ }
+
+ for _, r := range roles {
+ result += fmt.Sprintf("- %s: %s\n", r.ID, r.Name)
+ }
+
+ s.ChannelMessageSend(m.ChannelID, result)
+ return nil
+}
+
+func (a *aura) setup(s *discordgo.Session, m *discordgo.Message, parv []string) error {
+ if len(parv) != 3 {
+ return errors.New("aura: wrong number of params for setup")
+ }
+
+ role := parv[1]
+ url := parv[2]
+
+ ch, err := s.Channel(m.ChannelID)
+ if err != nil {
+ return err
+ }
+
+ gid := ch.GuildID
+
+ roles, err := s.GuildRoles(gid)
+ if err != nil {
+ return err
+ }
+
+ found := false
+ for _, r := range roles {
+ if r.ID == role {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ return errors.New("aura: Role not found")
+ }
+
+ a.state.PermRoles[gid] = role
+ a.state.DownloadURLs[gid] = url
+
+ s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Guild %s set up for recording url %s controlled by role %s", gid, url, role))
+
+ a.state.Save()
+ return nil
+}
+
+func (a *aura) djon(s *discordgo.Session, m *discordgo.Message, parv []string) error {
+ ch, err := s.Channel(m.ChannelID)
+ if err != nil {
+ return err
+ }
+
+ gid := ch.GuildID
+ creator := m.Author.Username
+
+ member, err := s.GuildMember(gid, m.Author.ID)
+ if err != nil {
+ return err
+ }
+
+ if member.Nick != "" {
+ creator = member.Nick
+ }
+
+ fname, err := genFname(creator)
+ if err != nil {
+ return err
+ }
+
+ _, ok := a.guildRecordings[gid]
+ if ok {
+ log.Println(a.guildRecordings)
+ return errors.New("aura: another recording is already in progress")
+ }
+
+ os.Mkdir(path.Join(*dataPrefix, gid), 0775)
+
+ rr, err := recording.New(a.state.DownloadURLs[gid], filepath.Join(*dataPrefix, gid, fname))
+ if err != nil {
+ return err
+ }
+
+ a.guildRecordings[gid] = &rec{
+ Recording: rr,
+ creator: creator,
+ }
+
+ go func() {
+ err := rr.Start()
+ if err != nil {
+ s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("recording error: %v", err))
+ return
+ }
+ }()
+
+ s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Now recording: `%s`\n\n%s get in here fam", fname, os.Getenv("NOTIFICATION_SQUAD_ID")))
+
+ go a.waitAndAnnounce(s, m, a.guildRecordings[gid], gid)
+
+ return nil
+}
+
+func (a *aura) waitAndAnnounce(s *discordgo.Session, m *discordgo.Message, r *rec, gid string) {
+ <-r.Done()
+
+ defer delete(a.guildRecordings, gid)
+
+ fname := r.OutputFilename()
+ parts := strings.Split(fname, "/")
+
+ slog.Info("stuff", "fname", fname, "parts", parts)
+
+ recurl := fmt.Sprintf("https://%s/var/%s/%s", *recordingDomain, parts[3], urlencode(parts[4]))
+ id, err := a.hid.EncodeInt64([]int64{int64(rand.Int())})
+ if err != nil {
+ s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("This state should be impossible. Recording saved but unknown short URL: %v", err))
+ return
+ }
+
+ a.state.Shorturls[id] = recurl
+ a.state.Save()
+
+ slink := fmt.Sprintf("https://%s/id/%s", *recordingDomain, id)
+
+ s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Recording complete (%s): %s", time.Now().Sub(r.StartTime()).String(), slink))
+}
+
+func urlencode(inp string) string {
+ return (&url.URL{Path: inp}).String()
+}
+
+func (a *aura) djoff(s *discordgo.Session, m *discordgo.Message, parv []string) error {
+ ch, err := s.Channel(m.ChannelID)
+ if err != nil {
+ return err
+ }
+
+ gid := ch.GuildID
+
+ r, ok := a.guildRecordings[gid]
+ if r == nil || !ok {
+ log.Println(a.guildRecordings)
+ return errors.New("aura: no recording is currently in progress")
+ }
+
+ if r.Err == nil {
+ s.ChannelMessageSend(m.ChannelID, "Finishing recording (waiting 30 seconds)")
+ time.Sleep(30 * time.Second)
+
+ r.Cancel()
+ }
+
+ return nil
+}
+
+func (a *aura) Handle(s *discordgo.Session, m *discordgo.MessageCreate) {
+ err := a.cs.Run(s, m.Message)
+ if err != nil {
+ log.Println(err)
+ }
+}
diff --git a/cmd/aura/pvfm.go b/cmd/aura/pvfm.go
new file mode 100644
index 0000000..06ab7d0
--- /dev/null
+++ b/cmd/aura/pvfm.go
@@ -0,0 +1 @@
+package main
diff --git a/cmd/aura/var/.gitignore b/cmd/aura/var/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/cmd/aura/var/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore \ No newline at end of file