simpbot/simp.go

298 lines
8.1 KiB
Go

package main
import (
"fmt"
"net/url"
"strings"
"time"
"github.com/charmbracelet/log"
"github.com/spf13/viper"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
type simp struct {
client *mautrix.Client
dimensionServer string
holodexToken string
startTime time.Time
currStream int
maxStream int
vtubers []*Vtuber
stop chan bool
rooms []id.RoomID
}
func newSimp() *simp {
return &simp{
startTime: time.Now(),
stop: make(chan bool),
}
}
func (s *simp) Run() {
ticker := time.NewTicker(time.Second * 45)
roomResp, err := s.client.JoinedRooms()
if err != nil {
log.Errorf("error getting joined rooms: %v", err)
return
}
s.rooms = roomResp.JoinedRooms
for {
select {
case <-s.stop:
return
case <-ticker.C:
// We're going to assume they're only stream one video at a time
for _, v := range s.vtubers {
err = v.Update(s.holodexToken)
if err != nil {
log.Error("error pinging holodex", "error", err)
}
for _, room := range s.rooms {
if v.IsLive() {
// check to see if already embeded
var content YoutubeWidget
err = s.client.StateEvent(
room,
event.NewEventType("im.vector.modular.widgets"),
"dimension-m.video-simp-"+v.Name,
&content,
)
if err != nil {
log.Errorf("error getting state event in room %v: %v", room, err)
}
if content.ID == "" {
if v.AnnounceLive {
s.client.SendText(room, v.LiveMsg)
} else {
if isValidURL(v.LiveMsg) {
s.client.SendNotice(room, fmt.Sprintf("%v has gone live", v.Name))
} else {
s.client.SendNotice(room, v.LiveMsg)
}
}
s.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.client.SendText(room, fmt.Sprintf("Pinging %v", subs))
}
resp, err := s.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 {
log.Errorf("error embeding video: %v", err)
}
v.TotalStreams = v.TotalStreams + 1
s.currStream++
if s.currStream > s.maxStream {
s.maxStream = s.currStream
}
log.Info("Embed stream added", "event", resp, "vtuber", v.Name)
}
} else {
var content YoutubeWidget
err = s.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 := s.client.SendStateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, struct{}{})
if err != nil {
log.Errorf("error removing embed: %v", err)
}
s.currStream--
log.Info("Embed stream removed", "event", resp, "vtuber", v.Name)
}
}
}
}
}
}
}
func (s *simp) SetupMatrix(uname, pass, token, hs, domain, dserver string) error {
uid := id.NewUserID(strings.ToLower(uname), strings.ToLower(domain))
if token == "" {
client, err := mautrix.NewClient(hs, "", "")
if err != nil {
return err
}
s.client = client
} else {
log.Info("using token login")
client, err := mautrix.NewClient(hs, uid, token)
if err != nil {
return err
}
s.client = client
}
dataFilter := &mautrix.Filter{
AccountData: mautrix.FilterPart{
Limit: 20,
NotTypes: []event.Type{
event.NewEventType("s.batch"),
},
},
}
store := mautrix.NewAccountDataStore("simp.batch", s.client)
fID, err := s.client.CreateFilter(dataFilter)
if err != nil {
return err
}
store.SaveFilterID(uid, fID.FilterID)
s.client.Store = store
if token == "" {
loginRes, err := s.client.Login(&mautrix.ReqLogin{
Type: "m.login.password",
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: uname},
Password: pass,
StoreCredentials: true,
})
if err != nil {
return err
}
token = loginRes.AccessToken
viper.Set("access_token", token)
log.Info("Login succesful, saving access_token to config file")
err = viper.WriteConfig()
if err != nil {
return err
}
} else {
log.Info("skipping login since token provided")
}
syncer := s.client.Syncer.(*mautrix.DefaultSyncer)
syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) {
if evt.Sender == s.client.UserID {
return // ignore events from self
}
log.Debugf("<%[1]s> %[4]s (%[2]s/%[3]s)\n", evt.Sender, evt.Type.String(), evt.ID, evt.Content.AsMessage().Body)
body := evt.Content.AsMessage().Body
bodyS := strings.Split(body, " ")
if bodyS[0] != "!simp" {
return
}
if len(bodyS) < 2 {
return // nothing to parse
}
switch bodyS[1] {
case "info":
// print info page
var infomsg string
vlist := []string{}
for _, vt := range s.vtubers {
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"))
s.client.SendText(evt.RoomID, infomsg)
case "stats":
var statmsg string
vlist := []string{}
t := 0
for _, vt := range s.vtubers {
vlist = append(vlist, fmt.Sprintf("%v Total:%v", vt.Name, vt.TotalStreams))
t = t + vt.TotalStreams
}
statmsg = fmt.Sprintf(
"Current Stats Since %v:\n%v\n\nTotal Streams: %v\nMost Concurrent: %v/%v\n",
s.startTime,
strings.Join(vlist, "\n"),
t,
s.maxStream,
len(s.vtubers),
)
s.client.SendText(evt.RoomID, statmsg)
case "version":
s.client.SendText(evt.RoomID, "not implemented")
case "reload":
// reload config
s.client.SendText(evt.RoomID, "Reloading config")
log.Info("Reload requested,reloading vtubers")
s.vtubers = loadVtubers()
case "subscribe":
if len(bodyS) < 3 {
s.client.SendText(evt.RoomID, "Need a member to subscribe to")
}
vt := bodyS[2]
var subbed bool
for _, v := range s.vtubers {
if strings.ToUpper(v.Name) == strings.ToUpper(vt) {
v.Subs[evt.Sender] = true
subbed = true
}
}
if subbed {
s.client.SendText(evt.RoomID, "subbed")
} else {
s.client.SendText(evt.RoomID, "could not identify talent to subscribe to")
}
case "help":
s.client.SendText(evt.RoomID, "Supported commands: info,version,stats,reload,subscribe")
default:
// command not found
s.client.SendText(evt.RoomID, "command not recognized")
}
})
syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) {
log.Infof("<%[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 = s.client.JoinRoomByID(evt.RoomID)
if err != nil {
log.Errorf("error joining room %v", evt.RoomID)
} else {
log.Infof("joined room %v", evt.RoomID)
s.rooms = append(s.rooms, evt.RoomID)
}
}
})
s.dimensionServer = dserver
return nil
}
func (s *simp) Stop() {
s.stop <- true
s.client.StopSync()
}
func (s *simp) NewYT(videoName, videoID, roomID string) *YoutubeWidget {
encodedVod := url.QueryEscape("https://youtube.com/embed/" + videoID)
return &YoutubeWidget{
Type: "im.vector.modular.widgets",
URL: "https://" + s.dimensionServer + "/widgets/video?url=" + encodedVod,
Name: videoName,
Data: VideoData{
VideoURL: "https://www.youtube.com/watch?v=" + videoID,
URL: "https://youtube.com/embed/" + videoID,
DimensionAppMetadata: DimensionAppMetadata{
InRoomID: roomID,
WrapperURLBase: "https://" + s.dimensionServer + "/widgets/video?url=",
WrapperID: "video",
ScalarWrapperID: "youtube",
Integration: Integration{
Category: "widget",
Type: "youtube",
},
LastUpdatedTs: time.Now().UnixNano() / int64(time.Millisecond),
},
},
CreatorUserID: string(s.client.UserID),
ID: "dimension-m.video-simp",
}
}