commit 45c63330a19c917499521e018147929d1008dc7b Author: Steve Date: Fri May 21 12:45:24 2021 -0400 initial diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e3570ca --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module simpbot + +go 1.15 + +require ( + golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect + golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect + maunium.net/go/mautrix v0.9.12 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a198020 --- /dev/null +++ b/go.sum @@ -0,0 +1,70 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +maunium.net/go/maulogger/v2 v2.2.4/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= +maunium.net/go/mautrix v0.9.12 h1:iNsKOCyK/BqN6Q/7A1aLj1B0/lMbr81LIg+Lry1Vjuo= +maunium.net/go/mautrix v0.9.12/go.mod h1:7IzKfWvpQtN+W2Lzxc0rLvIxFM3ryKX6Ys3S/ZoWbg8= diff --git a/live.go b/live.go new file mode 100644 index 0000000..c69bbc3 --- /dev/null +++ b/live.go @@ -0,0 +1,27 @@ +package main + +import "time" + +type StreamList struct { + Total string `json:"total"` + Streams []Stream `json:"items"` +} +type Channel struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Photo string `json:"photo"` + EnglishName string `json:"english_name"` +} +type Stream struct { + ID string `json:"id"` + Title string `json:"title"` + Type string `json:"type"` + TopicID string `json:"topic_id"` + PublishedAt time.Time `json:"published_at"` + AvailableAt time.Time `json:"available_at"` + Duration int `json:"duration"` + Status string `json:"status"` + StartScheduled time.Time `json:"start_scheduled"` + Channel Channel `json:"channel"` +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..fe9c96d --- /dev/null +++ b/main.go @@ -0,0 +1,111 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "time" + + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/event" +) + +var Homeserver = flag.String("homeserver", "", "Matrix homeserver") +var Username = flag.String("username", "", "Matrix username localpart") +var Password = flag.String("password", "", "Matrix password") +var DimensionServer = flag.String("dimension", "", "Dimension server URL") +var HomeserverDomain = flag.String("domain", "", "Matrix homserver domain (the part after : )") + +func main() { + flag.Parse() + if *Username == "" || *Password == "" || *Homeserver == "" || *DimensionServer == "" || *HomeserverDomain == "" { + _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + flag.PrintDefaults() + os.Exit(1) + } + + fmt.Println("Logging into", *Homeserver, "as", *Username) + client, err := mautrix.NewClient(*Homeserver, "", "") + if err != nil { + panic(err) + } + _, err = client.Login(&mautrix.ReqLogin{ + Type: "m.login.password", + Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: *Username}, + Password: *Password, + StoreCredentials: true, + }) + if err != nil { + panic(err) + } + fmt.Println("Login successful") + syncer := client.Syncer.(*mautrix.DefaultSyncer) + syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) { + fmt.Printf("<%[1]s> %[4]s (%[2]s/%[3]s)\n", evt.Sender, evt.Type.String(), evt.ID, evt.Content.AsMessage().Body) + }) + syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) { + fmt.Printf("<%[1]s> %[4]s (%[2]s/%[3]s)\n", evt.Sender, evt.Type.String(), evt.ID, evt.Content.AsMessage().Body) + if evt.Content.AsMember().Membership.IsInviteOrJoin() { + _, err = client.JoinRoomByID(evt.RoomID) + if err != nil { + fmt.Printf("error joining room %v", evt.RoomID) + } else { + fmt.Println("joined room") + } + } + }) + + vtubers := []*Vtuber{NewVtuber("Ina", "UCMwGHR0BTZuLsmjY_NT5Pwg", "The High Priestess is Live"), NewVtuber("Kiara", "UCHsx4Hqa-1ORjQTh9TYDhww", "The Bird has Landed"), NewVtuber("Calliope", "UCL_qhgtOy0dy1Agp8vkySQg", "The Reapers here")} + + go func() { + for { + time.Sleep(30 * time.Second) + roomResp, err := client.JoinedRooms() + if err != nil { + log.Println("error gettting joined rooms") + } + rooms := roomResp.JoinedRooms + // We're going to assume they're only stream one video at a time + for _, v := range vtubers { + v.Update() + if v.IsLive() { + for _, room := range rooms { + //check to see if already embeded + var content YoutubeWidget + err = client.StateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, &content) + if content.ID == "" { + client.SendText(room, v.LiveMsg) + resp, err := client.SendStateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, NewYT(v.Name+"'s stream", v.CurrentStream, string(room))) + if err != nil { + log.Println("error embeding video") + log.Println(err) + } + log.Printf("Embed event %v", resp) + } + } + } else { + //Not live, check to see if there's any embeds and remove them + for _, room := range rooms { + var content YoutubeWidget + err = client.StateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, &content) + if err == nil && content.ID != "" { + //event found, kill it + resp, err := client.SendStateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, struct{}{}) + if err != nil { + log.Println("error removed video embed") + log.Println(err) + } + log.Printf("Emebed remove %v", resp) + } + } + } + } + } + }() + + err = client.Sync() + if err != nil { + panic(err) + } +} diff --git a/vtuber.go b/vtuber.go new file mode 100644 index 0000000..6030eb0 --- /dev/null +++ b/vtuber.go @@ -0,0 +1,60 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" +) + +type Vtuber struct { + Name string + ChannelID string + CurrentStream string + LiveMsg string +} + +func NewVtuber(name, channelID, liveMsg string) *Vtuber { + return &Vtuber{ + Name: name, + ChannelID: channelID, + CurrentStream: "", + LiveMsg: liveMsg, + } +} + +func (v *Vtuber) IsLive() bool { + return v.CurrentStream != "" +} + +func (v *Vtuber) Update() error { + url := fmt.Sprintf("https://holodex.net/api/v2/live?channel_id=%s&lang=all&sort=available_at&order=desc&limit=25&offset=0&paginated=%3Cempty%3E", v.ChannelID) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + jsonBody, _ := ioutil.ReadAll(res.Body) + var sl StreamList + err = json.Unmarshal(jsonBody, &sl) + if err != nil { + log.Println("error parsing json for vtuber") + return err + } + found := false + for _, s := range sl.Streams { + if s.Status == "live" { + v.CurrentStream = s.ID + found = true + } + } + if !found { + v.CurrentStream = "" + } + return nil +} diff --git a/youtube.go b/youtube.go new file mode 100644 index 0000000..be89736 --- /dev/null +++ b/youtube.go @@ -0,0 +1,63 @@ +package main + +import ( + "net/url" + "time" +) + +type Integration struct { + Category string `json:"category"` + Type string `json:"type"` +} +type DimensionAppMetadata struct { + InRoomID string `json:"inRoomId"` + WrapperURLBase string `json:"wrapperUrlBase"` + WrapperID string `json:"wrapperId"` + ScalarWrapperID string `json:"scalarWrapperId"` + Integration Integration `json:"integration"` + LastUpdatedTs int64 `json:"lastUpdatedTs"` +} +type Data struct { + VideoURL string `json:"videoUrl"` + URL string `json:"url"` + DimensionAppMetadata DimensionAppMetadata `json:"dimension:app:metadata"` +} +type YoutubeWidget struct { + Type string `json:"type"` + URL string `json:"url"` + Name string `json:"name"` + Data Data `json:"data"` + CreatorUserID string `json:"creatorUserId"` + ID string `json:"id"` + RoomID string `json:"roomId"` + EventID string `json:"eventId"` +} +type Unsigned struct { + Age int `json:"age"` +} + +func NewYT(videoName, videoID, roomID string) *YoutubeWidget { + encodedVod := url.QueryEscape("https://youtube.com/embed/" + videoID) + return &YoutubeWidget{ + Type: "im.vector.modular.widgets", + URL: "https://" + *DimensionServer + "/widgets/video?url=" + encodedVod, + Name: videoName, + Data: Data{ + VideoURL: "https://www.youtube.com/watch?v=" + videoID, + URL: "https://youtube.com/embed/" + videoID, + DimensionAppMetadata: DimensionAppMetadata{ + InRoomID: roomID, + WrapperURLBase: "https://" + *DimensionServer + "/widgets/video?url=", + WrapperID: "video", + ScalarWrapperID: "youtube", + Integration: Integration{ + Category: "widget", + Type: "youtube", + }, + LastUpdatedTs: time.Now().UnixNano() / int64(time.Millisecond), + }, + }, + CreatorUserID: "@" + *Username + ":" + *HomeserverDomain, + ID: "dimension-m.video-simp", + } +}