package main import ( "fmt" "log" "net/url" "os" "os/signal" "strings" "syscall" "time" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" "maunium.net/go/mautrix" "maunium.net/go/mautrix/event" ) var Homeserver string var Username string var Password string var DimensionServer string var HomeserverDomain string var GitCommit string var GitTag string var Statefile string var CurrStreamCnt int var MostStreamCnt int var StartTime time.Time func main() { viper.SetConfigName("config") viper.AddConfigPath(".") viper.AddConfigPath("/etc/simpbot") err := viper.ReadInConfig() if err != nil { log.Fatalf("Fatal error config file: %v \n", err) } viper.SetConfigType("yaml") Homeserver = viper.GetString("homeserver") viper.SetDefault("domain", Homeserver) viper.SetDefault("statefile", "simpstate") Username = viper.GetString("username") Password = viper.GetString("password") DimensionServer = viper.GetString("dimension") HomeserverDomain = viper.GetString("domain") Statefile = viper.GetString("statefile") CurrStreamCnt = 0 MostStreamCnt = 0 StartTime = time.Now() var vtubers []*Vtuber log.Println("Logging into", Homeserver, "as", Username) client, err := mautrix.NewClient(Homeserver, "", "") if err != nil { panic(err) } client.Store = NewLazyMemStore(Statefile) _, 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) { if evt.Sender == client.UserID { return //ignore events from self } fmt.Printf("<%[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 body_s := strings.Split(body, " ") if body_s[0] != "!simp" { return } if len(body_s) < 2 { return //nothing to parse } switch body_s[1] { case "info": // print info page var infomsg string vlist := []string{} for _, vt := range 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")) client.SendText(evt.RoomID, infomsg) case "stats": var statmsg string vlist := []string{} t := 0 for _, vt := range 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", StartTime, strings.Join(vlist, "\n"), t, MostStreamCnt, len(vtubers)) client.SendText(evt.RoomID, statmsg) case "version": // print version if GitTag != "" { client.SendText(evt.RoomID, "SimpBot version "+GitTag) } else { client.SendText(evt.RoomID, "SimpBot version "+GitCommit) } case "reload": //reload config client.SendText(evt.RoomID, "Reloading config") fmt.Println("Reload requested,reloading vtubers") vtubers = LoadVtubers() case "help": client.SendText(evt.RoomID, "Supported commands: info,version,stats") default: //command not found client.SendText(evt.RoomID, "command not recognized") } }) 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.Printf("joined room %v", evt.RoomID) } } }) sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGUSR1) vtubers = LoadVtubers() viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { fmt.Println("Config file changed,reloading vtubers:", e.Name) vtubers = LoadVtubers() }) go func() { for { time.Sleep(30 * time.Second) roomResp, err := client.JoinedRooms() if err != nil { log.Println("error getting joined rooms") log.Println(err) log.Println("Skipping iteration") continue } 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 == "" { if v.AnnounceLive { client.SendText(room, v.LiveMsg) } else { if isValidUrl(v.LiveMsg) { client.SendNotice(room, fmt.Sprintf("%v has gone live", v.Name)) } else { client.SendNotice(room, v.LiveMsg) } } client.SendNotice(room, fmt.Sprintf("%v's Title: %v", v.Name, v.CurrentStreamTitle)) 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 stream %v for %v ", resp, v.Name) } } } 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("Embed stream %v removed %v", resp, v.Name) } } } } } }() err = client.Sync() if err != nil { panic(err) } } func isValidUrl(toTest string) bool { _, err := url.ParseRequestURI(toTest) if err != nil { return false } u, err := url.Parse(toTest) if err != nil || u.Scheme == "" || u.Host == "" { return false } return true }