simpbot/main.go
Steve de2e8d014a
All checks were successful
continuous-integration/drone/push Build is passing
check error
2023-05-14 17:39:08 -04:00

304 lines
8.6 KiB
Go

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
}