2023-05-14 18:45:55 -04:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2023-05-30 20:12:38 -04:00
|
|
|
"errors"
|
2023-05-14 18:45:55 -04:00
|
|
|
"fmt"
|
2023-05-30 20:12:38 -04:00
|
|
|
"io/ioutil"
|
2023-05-14 18:45:55 -04:00
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2023-05-30 20:12:38 -04:00
|
|
|
mbl "git.saintnet.tech/stryan/matrixbotlib"
|
2023-05-14 18:45:55 -04:00
|
|
|
"github.com/charmbracelet/log"
|
2023-06-01 17:45:43 -04:00
|
|
|
"golang.org/x/exp/slices"
|
2023-05-30 20:12:38 -04:00
|
|
|
"gopkg.in/yaml.v2"
|
2023-05-14 18:45:55 -04:00
|
|
|
"maunium.net/go/mautrix"
|
|
|
|
"maunium.net/go/mautrix/event"
|
|
|
|
"maunium.net/go/mautrix/id"
|
|
|
|
)
|
|
|
|
|
2023-05-30 20:12:38 -04:00
|
|
|
//SimpConf is the simpbot conf that's not matrix
|
|
|
|
type SimpConf struct {
|
|
|
|
DimensionServer string `yaml:"dimension"`
|
|
|
|
HolodexToken string `yaml:"api_token"`
|
|
|
|
Vtubers []vtuberConfig `yaml:"vtubers"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type simpState struct {
|
|
|
|
client *mautrix.Client
|
|
|
|
startTime time.Time
|
|
|
|
currStream int
|
|
|
|
maxStream int
|
|
|
|
vtubers []*Vtuber
|
|
|
|
stop chan bool
|
|
|
|
stopRun chan bool
|
|
|
|
change chan int
|
|
|
|
rooms []id.RoomID
|
|
|
|
filename string
|
|
|
|
}
|
|
|
|
|
2023-05-14 18:45:55 -04:00
|
|
|
type simp struct {
|
2023-05-30 20:12:38 -04:00
|
|
|
Config *SimpConf
|
|
|
|
State *simpState
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
|
|
|
|
2023-05-30 20:12:38 -04:00
|
|
|
func newSimp(filename string, client *mautrix.Client) (*simp, error) {
|
|
|
|
cnf := &SimpConf{}
|
|
|
|
state := &simpState{
|
2023-05-14 18:45:55 -04:00
|
|
|
startTime: time.Now(),
|
|
|
|
stop: make(chan bool),
|
2023-05-30 20:12:38 -04:00
|
|
|
stopRun: make(chan bool),
|
|
|
|
change: make(chan int),
|
|
|
|
client: client,
|
|
|
|
}
|
|
|
|
yamlFile, err := ioutil.ReadFile(filename)
|
|
|
|
if err == nil {
|
|
|
|
err = yaml.Unmarshal(yamlFile, cnf)
|
|
|
|
} else {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
state.filename = filename
|
|
|
|
for _, vt := range cnf.Vtubers {
|
|
|
|
log.Infof("adding vtuber %v", vt)
|
|
|
|
v := newVtuber(vt.Name, vt.ChannelID, vt.LiveMsg, vt.Announce)
|
|
|
|
state.vtubers = append(state.vtubers, v)
|
|
|
|
}
|
|
|
|
log.Print(cnf)
|
|
|
|
if len(state.vtubers) == 0 {
|
|
|
|
return nil, fmt.Errorf("no vtubers defined")
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
2023-05-30 20:12:38 -04:00
|
|
|
if cnf.HolodexToken == "" {
|
|
|
|
return nil, fmt.Errorf("no holodex token")
|
|
|
|
}
|
|
|
|
if cnf.DimensionServer == "" {
|
|
|
|
return nil, fmt.Errorf("no dimension server")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &simp{cnf, state}, nil
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
|
|
|
|
2023-05-30 20:12:38 -04:00
|
|
|
func (s *simp) Sync() error {
|
|
|
|
go s.Run()
|
|
|
|
for _, v := range s.State.vtubers {
|
|
|
|
err := v.Update(s.Config.HolodexToken)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("error pinging holodex", "error", err)
|
|
|
|
continue
|
|
|
|
}
|
2023-05-15 19:21:02 -04:00
|
|
|
}
|
2023-05-30 20:12:38 -04:00
|
|
|
|
|
|
|
for _, r := range s.State.rooms {
|
|
|
|
for _, v := range s.State.vtubers {
|
|
|
|
var content YoutubeWidget
|
|
|
|
err := s.State.client.StateEvent(
|
|
|
|
r,
|
|
|
|
event.NewEventType("im.vector.modular.widgets"),
|
|
|
|
"dimension-m.video-simp-"+v.Name,
|
|
|
|
&content,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
if !errors.Is(err, mautrix.MNotFound) {
|
|
|
|
log.Errorf("error getting state event in room %v: %v", r, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
embed := content.ID != ""
|
|
|
|
if v.IsLive() != embed {
|
|
|
|
s.Update(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log.Info("starting normal sync")
|
|
|
|
ticker := time.NewTicker(time.Second * 45)
|
2023-05-14 18:45:55 -04:00
|
|
|
for {
|
|
|
|
select {
|
2023-05-30 20:12:38 -04:00
|
|
|
case <-s.State.stop:
|
|
|
|
s.State.stopRun <- true
|
|
|
|
return nil
|
2023-05-14 18:45:55 -04:00
|
|
|
case <-ticker.C:
|
2023-05-30 20:12:38 -04:00
|
|
|
for i, v := range s.State.vtubers {
|
|
|
|
cur := v.IsLive()
|
|
|
|
err := v.Update(s.Config.HolodexToken)
|
2023-05-14 18:45:55 -04:00
|
|
|
if err != nil {
|
2023-05-14 19:09:49 -04:00
|
|
|
log.Error("error pinging holodex", "error", err)
|
2023-05-30 20:12:38 -04:00
|
|
|
continue
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
2023-05-30 20:12:38 -04:00
|
|
|
now := v.IsLive()
|
|
|
|
if cur != now {
|
|
|
|
s.State.change <- i
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-01 17:45:43 -04:00
|
|
|
func (s *simp) PopulateRoom(room id.RoomID) error {
|
|
|
|
log.Info("updating room", "room", room)
|
|
|
|
for _, v := range s.State.vtubers {
|
|
|
|
log.Printf("Updating vtuber %v", v.Name)
|
|
|
|
if v.IsLive() {
|
|
|
|
if v.AnnounceLive {
|
|
|
|
s.State.client.SendText(room, v.LiveMsg)
|
|
|
|
} else {
|
|
|
|
if isValidURL(v.LiveMsg) {
|
|
|
|
s.State.client.SendNotice(room, fmt.Sprintf("%v has gone live", v.Name))
|
|
|
|
} else {
|
|
|
|
s.State.client.SendNotice(room, v.LiveMsg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s.State.client.SendNotice(room, fmt.Sprintf("%v's Title: %v", v.Name, v.CurrentStreamTitle))
|
|
|
|
var subs string
|
|
|
|
for k := range v.Subs {
|
|
|
|
subs += k.String() + " "
|
|
|
|
}
|
|
|
|
if len(v.Subs) > 0 {
|
|
|
|
s.State.client.SendText(room, fmt.Sprintf("Pinging %v", subs))
|
|
|
|
}
|
|
|
|
resp, err := s.State.client.SendStateEvent(
|
|
|
|
room,
|
|
|
|
event.NewEventType("im.vector.modular.widgets"),
|
|
|
|
"dimension-m.video-simp-"+v.Name,
|
|
|
|
s.NewYT(v.Name+"'s stream", v.CurrentStream, string(room)),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
v.TotalStreams = v.TotalStreams + 1
|
|
|
|
s.State.currStream++
|
|
|
|
if s.State.currStream > s.State.maxStream {
|
|
|
|
s.State.maxStream = s.State.currStream
|
|
|
|
}
|
|
|
|
log.Info("Embed stream added", "event", resp, "vtuber", v.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-05-30 20:12:38 -04:00
|
|
|
func (s *simp) Update(v *Vtuber) error {
|
|
|
|
for _, room := range s.State.rooms {
|
|
|
|
if v.IsLive() {
|
|
|
|
if v.AnnounceLive {
|
|
|
|
s.State.client.SendText(room, v.LiveMsg)
|
|
|
|
} else {
|
|
|
|
if isValidURL(v.LiveMsg) {
|
|
|
|
s.State.client.SendNotice(room, fmt.Sprintf("%v has gone live", v.Name))
|
|
|
|
} else {
|
|
|
|
s.State.client.SendNotice(room, v.LiveMsg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s.State.client.SendNotice(room, fmt.Sprintf("%v's Title: %v", v.Name, v.CurrentStreamTitle))
|
|
|
|
var subs string
|
|
|
|
for k := range v.Subs {
|
|
|
|
subs += k.String() + " "
|
|
|
|
}
|
|
|
|
if len(v.Subs) > 0 {
|
|
|
|
s.State.client.SendText(room, fmt.Sprintf("Pinging %v", subs))
|
|
|
|
}
|
|
|
|
resp, err := s.State.client.SendStateEvent(
|
|
|
|
room,
|
|
|
|
event.NewEventType("im.vector.modular.widgets"),
|
|
|
|
"dimension-m.video-simp-"+v.Name,
|
|
|
|
s.NewYT(v.Name+"'s stream", v.CurrentStream, string(room)),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
v.TotalStreams = v.TotalStreams + 1
|
|
|
|
s.State.currStream++
|
|
|
|
if s.State.currStream > s.State.maxStream {
|
|
|
|
s.State.maxStream = s.State.currStream
|
|
|
|
}
|
|
|
|
log.Info("Embed stream added", "event", resp, "vtuber", v.Name)
|
|
|
|
} else {
|
|
|
|
resp, err := s.State.client.SendStateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, struct{}{})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.State.currStream--
|
|
|
|
log.Info("Embed stream removed", "event", resp, "vtuber", v.Name)
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-30 20:12:38 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *simp) Run() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-s.State.stopRun:
|
|
|
|
log.Info("stopping runner")
|
|
|
|
return
|
|
|
|
case v := <-s.State.change:
|
|
|
|
s.Update(s.State.vtubers[v])
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
|
|
|
}
|
2023-05-30 20:12:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *simp) SetupMatrix() error {
|
|
|
|
mbl.SetupAccountDataStore(s.State.client, "s.batch")
|
|
|
|
syncer := s.State.client.Syncer.(*mautrix.DefaultSyncer)
|
2023-06-01 17:45:43 -04:00
|
|
|
uid := s.State.client.UserID.String()
|
2023-05-14 18:45:55 -04:00
|
|
|
syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) {
|
2023-05-30 20:12:38 -04:00
|
|
|
if evt.Sender == s.State.client.UserID {
|
2023-05-14 19:13:23 -04:00
|
|
|
return // ignore events from self
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
|
|
|
log.Debugf("<%[1]s> %[4]s (%[2]s/%[3]s)\n", evt.Sender, evt.Type.String(), evt.ID, evt.Content.AsMessage().Body)
|
2023-05-30 20:12:38 -04:00
|
|
|
|
|
|
|
cmd, err := mbl.ParseCommand(evt, "simp")
|
|
|
|
if err != nil {
|
|
|
|
if err != mbl.ErrCmdParseNoPrefix {
|
|
|
|
log.Printf("invalid command: %v", err)
|
|
|
|
}
|
2023-05-14 18:45:55 -04:00
|
|
|
return
|
|
|
|
}
|
2023-05-30 20:12:38 -04:00
|
|
|
switch cmd[0] {
|
2023-05-14 18:45:55 -04:00
|
|
|
case "info":
|
|
|
|
// print info page
|
|
|
|
var infomsg string
|
|
|
|
vlist := []string{}
|
2023-05-30 20:12:38 -04:00
|
|
|
for _, vt := range s.State.vtubers {
|
2023-05-14 18:45:55 -04:00
|
|
|
ann := ""
|
|
|
|
if vt.AnnounceLive {
|
|
|
|
ann = "*"
|
|
|
|
}
|
|
|
|
vlist = append(vlist, fmt.Sprintf("%v%v", vt.Name, ann))
|
|
|
|
}
|
|
|
|
infomsg = fmt.Sprintf("Currently Simping For: \n%v", strings.Join(vlist, "\n"))
|
2023-05-30 20:12:38 -04:00
|
|
|
s.State.client.SendText(evt.RoomID, infomsg)
|
2023-05-14 18:45:55 -04:00
|
|
|
case "stats":
|
|
|
|
var statmsg string
|
|
|
|
vlist := []string{}
|
|
|
|
t := 0
|
2023-05-30 20:12:38 -04:00
|
|
|
for _, vt := range s.State.vtubers {
|
2023-05-14 18:45:55 -04:00
|
|
|
vlist = append(vlist, fmt.Sprintf("%v Total:%v", vt.Name, vt.TotalStreams))
|
|
|
|
t = t + vt.TotalStreams
|
|
|
|
}
|
2023-05-14 19:13:23 -04:00
|
|
|
statmsg = fmt.Sprintf(
|
|
|
|
"Current Stats Since %v:\n%v\n\nTotal Streams: %v\nMost Concurrent: %v/%v\n",
|
2023-05-30 20:12:38 -04:00
|
|
|
s.State.startTime,
|
2023-05-14 19:13:23 -04:00
|
|
|
strings.Join(vlist, "\n"),
|
|
|
|
t,
|
2023-05-30 20:12:38 -04:00
|
|
|
s.State.maxStream,
|
|
|
|
len(s.State.vtubers),
|
2023-05-14 19:13:23 -04:00
|
|
|
)
|
2023-05-30 20:12:38 -04:00
|
|
|
s.State.client.SendText(evt.RoomID, statmsg)
|
2023-05-14 18:45:55 -04:00
|
|
|
case "version":
|
2023-05-30 20:12:38 -04:00
|
|
|
s.State.client.SendText(evt.RoomID, "not implemented")
|
2023-05-14 18:45:55 -04:00
|
|
|
case "subscribe":
|
2023-05-30 20:12:38 -04:00
|
|
|
if len(cmd) < 3 {
|
|
|
|
s.State.client.SendText(evt.RoomID, "Need a member to subscribe to")
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
2023-05-30 20:12:38 -04:00
|
|
|
vt := cmd[1]
|
2023-05-14 18:45:55 -04:00
|
|
|
var subbed bool
|
2023-05-30 20:12:38 -04:00
|
|
|
for _, v := range s.State.vtubers {
|
2023-05-14 18:45:55 -04:00
|
|
|
if strings.ToUpper(v.Name) == strings.ToUpper(vt) {
|
|
|
|
v.Subs[evt.Sender] = true
|
|
|
|
subbed = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if subbed {
|
2023-05-30 20:12:38 -04:00
|
|
|
s.State.client.SendText(evt.RoomID, "subbed")
|
2023-05-14 18:45:55 -04:00
|
|
|
} else {
|
2023-05-30 20:12:38 -04:00
|
|
|
s.State.client.SendText(evt.RoomID, "could not identify talent to subscribe to")
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
case "help":
|
2023-05-30 20:12:38 -04:00
|
|
|
s.State.client.SendText(evt.RoomID, "Supported commands: info,version,stats,subscribe")
|
2023-05-14 18:45:55 -04:00
|
|
|
default:
|
2023-05-14 19:13:23 -04:00
|
|
|
// command not found
|
2023-05-30 20:12:38 -04:00
|
|
|
s.State.client.SendText(evt.RoomID, "command not recognized")
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
|
|
|
})
|
|
|
|
syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) {
|
2023-06-01 17:45:43 -04:00
|
|
|
//log.Infof("<%[1]s> %[4]s (%[2]s/%[3]s)", evt.Sender, evt.Type.String(), evt.ID, evt.Content.AsMessage().Body)
|
|
|
|
mevt := evt.Content.AsMember()
|
|
|
|
log.Printf("FBL %v", mevt.Membership)
|
|
|
|
if mevt.Membership == event.MembershipInvite {
|
2023-05-30 20:12:38 -04:00
|
|
|
_, err := s.State.client.JoinRoomByID(evt.RoomID)
|
2023-05-14 18:45:55 -04:00
|
|
|
if err != nil {
|
2023-05-14 19:09:49 -04:00
|
|
|
log.Errorf("error joining room %v", evt.RoomID)
|
2023-05-14 18:45:55 -04:00
|
|
|
} else {
|
|
|
|
log.Infof("joined room %v", evt.RoomID)
|
2023-05-30 20:12:38 -04:00
|
|
|
s.State.rooms = append(s.State.rooms, evt.RoomID)
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
2023-06-01 17:45:43 -04:00
|
|
|
} else if evt.Content.AsMember().Membership.IsLeaveOrBan() {
|
|
|
|
if evt.StateKey != nil && *evt.StateKey == uid {
|
|
|
|
log.Infof("leaving room %v", evt.RoomID)
|
|
|
|
index := slices.Index(s.State.rooms, evt.RoomID)
|
|
|
|
if index != -1 {
|
|
|
|
slices.Delete(s.State.rooms, index, index)
|
|
|
|
} else {
|
|
|
|
log.Warn("asked to leave room not in memory: %v", evt.RoomID)
|
|
|
|
}
|
|
|
|
}
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
|
|
|
})
|
2023-05-30 20:12:38 -04:00
|
|
|
roomResp, err := s.State.client.JoinedRooms()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.State.rooms = roomResp.JoinedRooms
|
2023-06-01 17:45:43 -04:00
|
|
|
log.Infof("initial room count: %v", len(s.State.rooms))
|
2023-05-14 18:45:55 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *simp) Stop() {
|
2023-05-30 20:12:38 -04:00
|
|
|
s.State.stop <- true
|
|
|
|
s.State.client.StopSync()
|
2023-05-14 18:45:55 -04:00
|
|
|
}
|
|
|
|
|
2023-05-14 21:05:52 -04:00
|
|
|
func (s *simp) NewYT(videoName, videoID, roomID string) *YoutubeWidget {
|
2023-05-14 18:45:55 -04:00
|
|
|
encodedVod := url.QueryEscape("https://youtube.com/embed/" + videoID)
|
2023-05-14 21:05:52 -04:00
|
|
|
return &YoutubeWidget{
|
2023-05-14 18:45:55 -04:00
|
|
|
Type: "im.vector.modular.widgets",
|
2023-05-30 20:12:38 -04:00
|
|
|
URL: "https://" + s.Config.DimensionServer + "/widgets/video?url=" + encodedVod,
|
2023-05-14 18:45:55 -04:00
|
|
|
Name: videoName,
|
2023-05-14 21:05:52 -04:00
|
|
|
Data: VideoData{
|
2023-05-14 18:45:55 -04:00
|
|
|
VideoURL: "https://www.youtube.com/watch?v=" + videoID,
|
|
|
|
URL: "https://youtube.com/embed/" + videoID,
|
2023-05-14 21:05:52 -04:00
|
|
|
DimensionAppMetadata: DimensionAppMetadata{
|
2023-05-14 18:45:55 -04:00
|
|
|
InRoomID: roomID,
|
2023-05-30 20:12:38 -04:00
|
|
|
WrapperURLBase: "https://" + s.Config.DimensionServer + "/widgets/video?url=",
|
2023-05-14 18:45:55 -04:00
|
|
|
WrapperID: "video",
|
|
|
|
ScalarWrapperID: "youtube",
|
2023-05-14 21:05:52 -04:00
|
|
|
Integration: Integration{
|
2023-05-14 18:45:55 -04:00
|
|
|
Category: "widget",
|
|
|
|
Type: "youtube",
|
|
|
|
},
|
|
|
|
LastUpdatedTs: time.Now().UnixNano() / int64(time.Millisecond),
|
|
|
|
},
|
|
|
|
},
|
2023-05-30 20:12:38 -04:00
|
|
|
CreatorUserID: string(s.State.client.UserID),
|
2023-05-14 18:45:55 -04:00
|
|
|
ID: "dimension-m.video-simp",
|
|
|
|
}
|
|
|
|
}
|