package main import ( "fmt" "log" "strings" "maunium.net/go/mautrix" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" ) type Dealer struct { RoomsList map[id.RoomID]*Match PlayerRooms map[id.UserID]id.RoomID Client *mautrix.Client QueueRoom id.RoomID HomeserverDomain string } func (d *Dealer) ConnectToMatrix(homeserver, uname, passwd string) { log.Println("Logging into", homeserver, "as", uname) client, err := mautrix.NewClient(homeserver, "", "") if err != nil { panic(err) } client.Store = NewLazyMemStore("dealer_state") _, err = client.Login(&mautrix.ReqLogin{ Type: "m.login.password", Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: uname}, Password: passwd, StoreCredentials: true, }) if err != nil { panic(err) } log.Println("Login successful") d.Client = client } func (d *Dealer) RegisterHandlers() { syncer := d.Client.Syncer.(*mautrix.DefaultSyncer) syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) { if evt.Sender == d.Client.UserID { return } if evt.RoomID == d.QueueRoom { d.QueueEvent(evt) return } proom, ok := d.PlayerRooms[evt.Sender] if ok { log.Printf("membership event for player room %v", proom) if evt.Content.AsMember().Membership.IsLeaveOrBan() { delete(d.PlayerRooms, evt.Sender) return } } if evt.Content.AsMember().IsDirect { //intention to create player room if evt.Content.AsMember().Membership.IsInviteOrJoin() { d.PlayerRooms[evt.Sender] = evt.RoomID _, err := d.Client.JoinRoomByID(evt.RoomID) if err != nil { panic(err) } } else if evt.Content.AsMember().Membership.IsLeaveOrBan() { delete(d.PlayerRooms, evt.Sender) } return } match, ok := d.RoomsList[evt.RoomID] if !ok { log.Printf("received membership event for non active game: %v", evt) return } if evt.Sender != match.P1 && evt.Sender != match.P2 && evt.Sender != d.Client.UserID { log.Println("membership event for non player") return } members, err := d.Client.JoinedMembers(evt.RoomID) if err != nil { panic(err) } match.JoinOrLeave(members.Joined, evt.Content.AsMember()) }) syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) { playerRoom, pok := d.PlayerRooms[evt.Sender] if pok { log.Printf("received direct message in room %v", playerRoom) var roominfo PlayerRoomInfo _ = d.Client.StateEvent(playerRoom, event.Type{"snen.player", event.StateEventType}, "player_info", &roominfo) if roominfo.CurrentGame != "" { //parse as gamecommand match, ok := d.RoomsList[roominfo.CurrentGame] if !ok { log.Println("Dealer thinks a game is happening, but no game found") return } if match.Game == nil { d.Client.SendText(playerRoom, "can't accept game commands yet") return } resp := match.ParseAction(evt.Sender, evt.Content.AsMessage().Body) if resp.Body != "" { _, err := d.Client.SendText(playerRoom, resp.Body) if err != nil { panic(err) } d.Client.SendText(roominfo.CurrentGame, fmt.Sprintf("%v played %v", evt.Sender, evt.Content.AsMessage().Body)) if !resp.Private { d.Client.SendText(roominfo.CurrentGame, fmt.Sprintf("Result: %v", resp.Body)) } } } else { //parse as out of game command log.Println("out of game command received") //TODO make this better msg := evt.Content.AsMessage().Body msg_s := strings.Split(msg, " ") if msg_s[0] == "direct" { d.NewMatch(evt.Sender, id.NewUserID(msg_s[1], d.HomeserverDomain)) } } return } match, ok := d.RoomsList[evt.RoomID] if !ok { log.Println("received message event for non active game") return } if !match.IsPlayer(evt.Sender) { log.Println("ignoring message event from non player") return } //parse as in game command i.e. chat }) } func (d *Dealer) SetupRooms(domain string) { room := "#tomequeue:" + domain // try creating the room first res, createErr := d.Client.CreateRoom(&mautrix.ReqCreateRoom{ Preset: "public_chat", Visibility: "public", RoomAliasName: "tomequeue", }) if createErr != nil { //okay let's try joining the room res, err := d.Client.JoinRoom(room, "", nil) if err != nil { log.Println("unable to create or join queue room") log.Printf("Create err: %v", createErr) panic(err) } log.Printf("succesfully joined %v", res.RoomID) d.QueueRoom = res.RoomID } else { resJoin, err := d.Client.JoinRoomByID(res.RoomID) if err != nil { log.Println("error joined recently created queue room") panic(err) } log.Printf("succesfully created and joined %v", resJoin.RoomID) d.QueueRoom = resJoin.RoomID } //load player rooms log.Println("Loading known player rooms") joinedResp, err := d.Client.JoinedRooms() if err != nil { panic(err) } for _, r := range joinedResp.JoinedRooms { tags, err := d.Client.GetTags(r) if err != nil { log.Printf("can't get tags for room %v:%v", r, err) continue } _, ok := tags.Tags["tome_player_room"] if !ok { continue } memberListResp, err := d.Client.JoinedMembers(r) if err != nil { continue } keys := make([]id.UserID, 0, len(memberListResp.Joined)) for k := range memberListResp.Joined { keys = append(keys, k) } if len(keys) != 2 { log.Printf("tagged player room %v has more or less then 2 members:%v", r, len(keys)) if len(keys) == 1 && keys[0] == d.Client.UserID { //clean up old player room log.Printf("leaving player room %v that only has us in it", r) d.Client.LeaveRoom(r) } continue } for _, k := range keys { if k != d.Client.UserID { d.PlayerRooms[k] = r d.Client.SendStateEvent(r, event.Type{"snen.player", event.StateEventType}, "player_info", PlayerRoomInfo{ CurrentGame: "", CurrentDeck: []int{}, }) } } } log.Println("initial room setup complete") } func (d *Dealer) NewMatch(p1, p2 id.UserID) *Match { //creates a new matrix room for the match match := NewMatch() roomname := fmt.Sprintf("tome_match_%v", match.ID) createRes, err := d.Client.CreateRoom(&mautrix.ReqCreateRoom{ Preset: "public_chat", RoomAliasName: roomname, Invite: []id.UserID{p1, p2}, }) if err != nil { log.Println("error creating matrix room for match") panic(err) } match.MatrixRoom = createRes.RoomID match.P1 = p1 _, found := d.PlayerRooms[p1] if !found { d.NewPlayerRoom(p1) } match.P2 = p2 _, found = d.PlayerRooms[p2] if !found { d.NewPlayerRoom(p2) } match.P1Room = d.PlayerRooms[p1] match.P2Room = d.PlayerRooms[p2] match.Dealer = d.Client.UserID d.RoomsList[createRes.RoomID] = match d.Client.SendStateEvent(match.P1Room, event.Type{"snen.player", event.StateEventType}, "player_info", PlayerRoomInfo{ CurrentGame: match.MatrixRoom, CurrentDeck: []int{}, }) d.Client.SendStateEvent(match.P2Room, event.Type{"snen.player", event.StateEventType}, "player_info", PlayerRoomInfo{ CurrentGame: match.MatrixRoom, CurrentDeck: []int{}, }) return match } func (d *Dealer) QueueEvent(evt *event.Event) { mem := evt.Content.AsMember() if mem.Membership == event.MembershipJoin { log.Printf("Player %v has queued", evt.Sender) _, ok := d.PlayerRooms[evt.Sender] if !ok { //open up DM with player err := d.NewPlayerRoom(evt.Sender) if err != nil { panic(err) } } } else if mem.Membership.IsLeaveOrBan() { log.Printf("Player %v has dequeued", evt.Sender) } return } func (d *Dealer) NewPlayerRoom(user id.UserID) error { createRes, err := d.Client.CreateRoom(&mautrix.ReqCreateRoom{ Preset: "private_chat", Invite: []id.UserID{user}, IsDirect: true, }) if err != nil { log.Println("error creating matrix room for player") return err } d.PlayerRooms[user] = createRes.RoomID err = d.Client.AddTag(createRes.RoomID, "tome_player_room", 0.0) if err != nil { return err } return nil }