main.go (5145B)
1 package main 2 3 import ( 4 "context" 5 "errors" 6 "flag" 7 "net/http" 8 "os" 9 "time" 10 11 "github.com/prometheus/client_golang/prometheus" 12 "github.com/prometheus/client_golang/prometheus/promhttp" 13 "github.com/prometheus/common/version" 14 log "github.com/sirupsen/logrus" 15 "maunium.net/go/mautrix" 16 "maunium.net/go/mautrix/event" 17 "maunium.net/go/mautrix/id" 18 ) 19 20 const ( 21 collector = "matrix_ping_exporter" 22 ) 23 24 func main() { 25 var config = ReadConfig() 26 var address = config.Address 27 flag.StringVar(&address, "address", "0.0.0.0:8080", "Address to listen on") 28 flag.Parse() 29 30 // Create a matrix client for each homeserver 31 config.OwnHomeserver.Client = createMatrixClient(&config, &config.OwnHomeserver) 32 for i := range config.RemoteHomeservers { 33 config.RemoteHomeservers[i].Client = createMatrixClient(&config, &config.RemoteHomeservers[i]) 34 } 35 36 pingCollector := &PingCollector{ 37 Config: &config, 38 Mean: prometheus.NewGaugeVec(prometheus.GaugeOpts{ 39 Namespace: collector, 40 Name: "mean", 41 Help: "Mean ping time", 42 }, []string{"homeserver", "origin", "direction"}), 43 Median: prometheus.NewGaugeVec(prometheus.GaugeOpts{ 44 Namespace: collector, 45 Name: "median", 46 Help: "Median ping time", 47 }, []string{"homeserver", "origin", "direction"}), 48 GMean: prometheus.NewGaugeVec(prometheus.GaugeOpts{ 49 Namespace: collector, 50 Name: "gmean", 51 Help: "GMean ping time", 52 }, []string{"homeserver", "origin", "direction"}), 53 Failures: prometheus.NewCounterVec(prometheus.CounterOpts{ 54 Namespace: collector, 55 Name: "failures", 56 Help: "Ping failures", 57 }, []string{"origin", "direction"}), 58 } 59 60 go func() { 61 pingCollector.UpdateData() 62 time.Sleep(time.Duration(config.PingRateSeconds+1) * time.Second) 63 for { 64 pingCollector.UpdateData() 65 time.Sleep(time.Duration(config.PingRateSeconds+1) * time.Second) 66 } 67 }() 68 69 reg := prometheus.NewRegistry() 70 reg.MustRegister(version.NewCollector(collector)) 71 reg.MustRegister(pingCollector) 72 73 http.Handle("/metrics", promhttp.HandlerFor( 74 reg, 75 promhttp.HandlerOpts{}, 76 )) 77 78 log.Infof("Starting http server - %s", address) 79 if err := http.ListenAndServe(address, nil); err != nil { 80 log.Errorf("Failed to start http server: %s", err) 81 } 82 } 83 84 type PongEventRelation struct { 85 RelType string `json:"rel_type"` 86 EventID string `json:"event_id"` 87 } 88 89 type PongEventContent struct { 90 MsgType string `json:"msgtype"` 91 Body string `json:"body"` 92 RelatesTo PongEventRelation `json:"m.relates_to"` 93 } 94 95 func createMatrixClient(config *Config, homeserver *MatrixConfig) *mautrix.Client { 96 var startTime = time.Now() 97 98 client, err := mautrix.NewClient(homeserver.Homeserver, id.UserID(homeserver.Username), "") 99 if err != nil { 100 log.Errorf("Failed to create client for %s: %s", homeserver.Homeserver, err) 101 os.Exit(1) 102 } 103 104 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 105 defer cancel() 106 if homeserver.AccessToken == "" { 107 loginresp, err := client.Login(ctx, &mautrix.ReqLogin{ 108 Type: mautrix.AuthTypePassword, 109 Identifier: mautrix.UserIdentifier{Type: "m.id.user", User: homeserver.Username}, 110 Password: homeserver.Password, 111 StoreCredentials: true, 112 StoreHomeserverURL: true, 113 }) 114 if err != nil { 115 log.Errorf("Failed to login to %s: %s", homeserver.Homeserver, err) 116 os.Exit(1) 117 } 118 log.Infof("Logged in as %s", loginresp.UserID) 119 120 // Tell user to store the access token and device id and remove the password from the config 121 log.Warnf("Please store the access token and device id for %s and remove the password from the config", homeserver.Homeserver) 122 log.Warnf("Access token: %s", loginresp.AccessToken) 123 log.Warnf("Device ID: %s", loginresp.DeviceID) 124 } else { 125 client.AccessToken = homeserver.AccessToken 126 client.DeviceID = id.DeviceID(homeserver.DeviceID) 127 } 128 129 // join the ping room 130 resp, err := client.JoinRoom(ctx, config.PingRoom, "", nil) 131 if err != nil { 132 log.Errorf("Failed to join ping room: %s", err) 133 os.Exit(1) 134 } 135 log.Infof("Joined ping room %s", resp.RoomID) 136 config.PingRoomID = resp.RoomID 137 138 syncer := client.Syncer.(*mautrix.DefaultSyncer) 139 syncer.OnEventType(event.EventMessage, func(ctx context.Context, evt *event.Event) { 140 if evt.RoomID != config.PingRoomID { 141 return 142 } 143 if evt.Sender == client.UserID { 144 return 145 } 146 // Make sure the message is not older than startTime 147 if evt.Timestamp < startTime.Unix()*1000 { 148 return 149 } 150 151 if evt.Content.AsMessage().Body == "!ping" { 152 log.Infoln("Received ping message in pingroom by", evt.Sender) 153 // Respond to the ping 154 pong_event_content := PongEventContent{ 155 MsgType: "m.notice", 156 Body: "Pong!", 157 RelatesTo: PongEventRelation{ 158 RelType: "xyz.maubot.pong", 159 EventID: evt.ID.String(), 160 }, 161 } 162 _, err := client.SendMessageEvent(ctx, evt.RoomID, event.EventMessage, pong_event_content) 163 if err != nil { 164 log.Errorf("Failed to send pong: %s", err) 165 } 166 } 167 }) 168 169 go func() { 170 err = client.Sync() 171 if err != nil && !errors.Is(err, context.Canceled) { 172 log.Errorf("Failed to sync: %s", err) 173 os.Exit(1) 174 } 175 }() 176 177 return client 178 }