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" "maunium.net/go/mautrix/id" ) var Homeserver string var Username string var Password string var DimensionServer string var HomeserverDomain string var Token string var GitCommit string var GitTag string var Statefile string var CurrStreamCnt int var MostStreamCnt int var StartTime time.Time var HolodexToken string 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") Token = viper.GetString("access_token") HolodexToken = viper.GetString("api_token") 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) var client *mautrix.Client uid := id.NewUserID(strings.ToLower(Username), strings.ToLower(HomeserverDomain)) if Token == "" { client, err = mautrix.NewClient(Homeserver, "", "") if err != nil { panic(err) } } else { log.Println("using token login") client, err = mautrix.NewClient(Homeserver, uid, Token) if err != nil { panic(err) } } dataFilter := &mautrix.Filter{ AccountData: mautrix.FilterPart{ Limit: 20, NotTypes: []event.Type{ event.NewEventType("simp.batch"), }, }, } store := mautrix.NewAccountDataStore("simp.batch", client) fID, err := client.CreateFilter(dataFilter) store.SaveFilterID(uid, fID.FilterID) if err != nil { panic(err) } client.Store = store if Token == "" { login_res, 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) } Token = login_res.AccessToken viper.Set("access_token", Token) log.Println("Login succesful, saving access_token to config file") err = viper.WriteConfig() if err != nil { panic(err) } } else { log.Println("skipping login since token provided") } if HolodexToken == "" { log.Println("No holodex API token provided, unlikely to be able to get streams") } 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 "subscribe": if len(body_s) < 3 { client.SendText(evt.RoomID, "Need a member to subscribe to") } vt := body_s[2] var subbed bool for _, v := range vtubers { if strings.ToUpper(v.Name) == strings.ToUpper(vt) { v.Subs[evt.Sender] = true subbed = true } } if subbed { client.SendText(evt.RoomID, "subbed") } else { client.SendText(evt.RoomID, "could not identify talent to subscribe to") } case "help": client.SendText(evt.RoomID, "Supported commands: info,version,stats,reload,subscribe") 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 { err = v.Update(HolodexToken) if err != nil { log.Println(err) } 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 err != nil { log.Printf("error getting state event in room %v: %v", room, err) continue } 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)) var subs string for k := range v.Subs { subs += k.String() + " " } if len(v.Subs) > 0 { client.SendText(room, fmt.Sprintf("Pinging %v", subs)) } 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) } v.TotalStreams = v.TotalStreams + 1 CurrStreamCnt = CurrStreamCnt + 1 if CurrStreamCnt > MostStreamCnt { MostStreamCnt = CurrStreamCnt } 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 removing video embed") log.Println(err) } CurrStreamCnt = CurrStreamCnt - 1 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 }