diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bb14cf9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "Codegeex.RepoIndex": true +} \ No newline at end of file diff --git a/WatchCat b/WatchCat new file mode 100644 index 0000000..09f4dfe Binary files /dev/null and b/WatchCat differ diff --git a/api/handler.go b/api/handler.go new file mode 100644 index 0000000..e600f31 --- /dev/null +++ b/api/handler.go @@ -0,0 +1,452 @@ +package api + +import ( + "database/sql" + "encoding/json" + "github.com/MeowLynxSea/Uptimeow/config" + "github.com/MeowLynxSea/Uptimeow/internal/rcon" + _ "github.com/glebarez/sqlite" + "github.com/gorilla/websocket" + "github.com/robfig/cron/v3" + "github.com/wanghuiyt/ding" + "log" + "net/http" + "strconv" + "strings" + "time" +) + +var GlobalConfig config.ConfigData +var saveCron = cron.New() +var isOnline bool +var tps, tps5, tps15 float64 +var onlinePlayer, maxPlayer int +var playerList []string +var db *sql.DB +var warnLevel int + +const ( + warnLevelNormal = 0 + warnLevelWarning = 1 + warnLevelCritical = 2 +) + +type ServerData struct { + Time time.Time `json:"time"` + IsOnline bool `json:"is_online"` + Tps float64 `json:"tps"` + OnlinePlayer int `json:"online_player"` + MaxPlayer int `json:"max_player"` +} + +// Response 是发送给WebSocket客户端的响应结构 +type Response struct { + Code int `json:"code"` + Data []ServerData `json:"data"` +} + +type ServerInfo struct { + ServerName string `json:"server_name"` + ServerAddress string `json:"server_address"` + ServerWebsite string `json:"server_website"` + ServerDescription string `json:"server_description"` +} + +type DetailedInfo struct { + Time time.Time `json:"time"` + IsOnline bool `json:"is_online"` + Tps float64 `json:"tps"` + OnlinePlayer int `json:"online_player"` + MaxPlayer int `json:"max_player"` + PlayerList string `json:"player_list,omitempty"` +} + +func pushDingTalkBot(message string, msgtype string) { + if GlobalConfig.Warn.DingTalkBot.Enabled { + dingMsger := ding.Webhook{ + AccessToken: GlobalConfig.Warn.DingTalkBot.AccessToken, + Secret: GlobalConfig.Warn.DingTalkBot.Secret, + } + if GlobalConfig.Warn.DingTalkBot.AtMobile != "" { + err := dingMsger.SendMessageText(message, GlobalConfig.Warn.DingTalkBot.AtMobile) + if err != nil { + log.Println("[ERROR] 钉钉机器人推送失败,原因: ", err) + } else { + log.Println("[INFO] 钉钉机器人推送[" + msgtype + "]成功") + } + } else { + err := dingMsger.SendMessageText(message) + if err != nil { + log.Println("[ERROR] 钉钉机器人推送失败,原因: ", err) + } else { + log.Println("[INFO] 钉钉机器人推送[" + msgtype + "]成功") + } + } + } +} + +func init() { + GlobalConfig = config.Load() + warnLevel = 0 + + pushDingTalkBot("【成功】Uptimeow 监控已上线", "成功消息") + + db, err := sql.Open("sqlite", "data/history.db") + if err != nil { + log.Fatal(err) + } + + // 确保数据库连接是有效的 + err = db.Ping() + if err != nil { + log.Fatal(err) + } + + // 创建表data + createTableSQL := ` + CREATE TABLE IF NOT EXISTS data ( + time_index DATETIME NOT NULL PRIMARY KEY, + online BOOLEAN, + tps INTEGER, + online_player INTEGER, + max_player INTEGER, + player_list TEXT + ); + ` + _, err = db.Exec(createTableSQL) + if err != nil { + log.Fatal(err) + } + + saveCron.AddFunc("@every 10s", func() { + currentTime := time.Now() + // log.Println("[DEBUG] Saving data to database") + err = db.Ping() + if err != nil { + log.Println(err) + } + if !isOnline { + _, err = db.Exec("INSERT INTO data (time_index, online, tps, online_player, max_player, player_list) VALUES (?, ?, ?, ?, ?, ?)", currentTime.Format("2006-01-02 15:04:05"), 0, tps, 0, 0, "") + } else { + if tps != 0 { + _, err = db.Exec("INSERT INTO data (time_index, online, tps, online_player, max_player, player_list) VALUES (?, ?, ?, ?, ?, ?)", currentTime.Format("2006-01-02 15:04:05"), isOnline, tps, onlinePlayer, maxPlayer, strings.Join(playerList, ",")) + } + } + if err != nil { + log.Println("[ERROR] Failed to insert data into database:] ", err) + } + + switch warnLevel { + case warnLevelNormal: + if !isOnline && GlobalConfig.Warn.EnabledType.Offline { + warnLevel = warnLevelCritical + pushDingTalkBot("【紧急】服务器离线\n经监测,服务器已离线,请尽快处理\n时间:"+currentTime.Format("2006-01-02 15:04:05"), "异常告警") + break + } + if tps < GlobalConfig.Warn.EnabledType.LowTps.Threold && GlobalConfig.Warn.EnabledType.LowTps.Enabled && tps != 0 { + warnLevel = warnLevelWarning + pushDingTalkBot("【警告】TPS过低报警\n服务器TPS低于设定值("+strconv.FormatFloat(GlobalConfig.Warn.EnabledType.LowTps.Threold, 'f', 2, 64)+")\n当前TPS:"+strconv.FormatFloat(tps, 'f', 2, 64)+"\n时间:"+currentTime.Format("2006-01-02 15:04:05"), "异常告警") + } + case warnLevelWarning: + if !isOnline && GlobalConfig.Warn.EnabledType.Offline { + warnLevel = warnLevelCritical + pushDingTalkBot("【紧急】服务器离线\n经监测,服务器已离线,请尽快处理\n时间:"+currentTime.Format("2006-01-02 15:04:05"), "异常告警") + break + } + if tps >= GlobalConfig.Warn.EnabledType.LowTps.Threold && GlobalConfig.Warn.EnabledType.LowTps.Enabled { + warnLevel = warnLevelNormal + pushDingTalkBot("【恢复】服务器TPS恢复正常\n时间:"+currentTime.Format("2006-01-02 15:04:05"), "成功消息") + } + case warnLevelCritical: + if isOnline && GlobalConfig.Warn.EnabledType.Offline { + warnLevel = warnLevelNormal + pushDingTalkBot("【恢复】服务器已恢复在线\n时间:"+currentTime.Format("2006-01-02 15:04:05"), "成功消息") + } + } + }) + + saveCron.Start() + + isOnline = false + go rcon.InitRcon(callback) +} + +func toInt(value interface{}) int { + switch v := value.(type) { + case int: + return v + case int64: + return int(v) + case float64: + return int(v) + case string: + i, _ := strconv.Atoi(v) + return i + default: + return 0 + } +} + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + // 允许所有跨域请求,或者你可以在这里添加更复杂的验证逻辑 + return true + }, +} + +// WebSocketHandler 处理WebSocket连接 +func WebSocketHandler(w http.ResponseWriter, r *http.Request) { + localDB, err := sql.Open("sqlite", "data/history.db") + if err != nil { + log.Fatal(err) + } + defer localDB.Close() + + // 确保数据库连接是有效的 + err = localDB.Ping() + if err != nil { + log.Fatal(err) + } + + // 将HTTP连接升级为WebSocket连接 + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println("Error upgrading to WebSocket:", err) + return + } + defer conn.Close() + + // 在这里实现WebSocket的消息处理逻辑 + for { + _, message, err := conn.ReadMessage() + if err != nil { + log.Println("Error reading message:", err) + break + } + + var resp Response + clientTimeFormat := "2006/01/02 15:04:05" + switch { + case string(message)[:13] == "earlier than ": + // 解析时间并获取数据 + // log.Println("Received earlier than request") + reqTime, err := time.Parse(clientTimeFormat, strings.TrimPrefix(string(message), "earlier than ")) + if err != nil { + log.Println("Error parsing time:", err) + continue + } + resp.Data, err = getEarlierData(localDB, reqTime) + if err != nil { + log.Println("Error getting data from database:", err) + continue + } + case string(message)[:11] == "later than ": + // 解析时间并获取数据 + // log.Println("Received later than request") + reqTime, err := time.Parse(clientTimeFormat, strings.TrimPrefix(string(message), "later than ")) + if err != nil { + log.Println("Error parsing time:", err) + continue + } + resp.Data, err = getLaterData(localDB, reqTime) + if err != nil { + log.Println("Error getting data from database:", err) + continue + } + default: + log.Println("Received unknown command " + string(message)) + continue + } + + if err != nil { + log.Println("Error getting data from database:", err) + continue + } + + resp.Code = 200 + // 序列化数据为JSON + jsonResp, err := json.Marshal(resp) + if err != nil { + log.Println("Error marshaling response:", err) + continue + } + + // 发送数据给客户端 + if err := conn.WriteMessage(websocket.TextMessage, jsonResp); err != nil { + log.Println("Error writing message:", err) + break + } + } +} + +func getLaterData(database *sql.DB, t time.Time) ([]ServerData, error) { + dbTime := t.Format("2006-01-02 15:04:05") + query := `SELECT time_index, online, tps, online_player, max_player + FROM data WHERE time_index > ? ORDER BY time_index ASC` + rows, err := database.Query(query, dbTime) + if err != nil { + return nil, err + } + defer rows.Close() + + var data []ServerData + for rows.Next() { + var sd ServerData + if err := rows.Scan(&sd.Time, &sd.IsOnline, &sd.Tps, &sd.OnlinePlayer, &sd.MaxPlayer); err != nil { + return nil, err + } + data = append(data, sd) + } + return data, nil +} + +func getEarlierData(database *sql.DB, t time.Time) ([]ServerData, error) { + dbTime := t.Format("2006-01-02 15:04:05") + query := `SELECT time_index, online, tps, online_player, max_player + FROM data WHERE time_index < ? ORDER BY time_index DESC LIMIT 60` + rows, err := database.Query(query, dbTime) + if err != nil { + return nil, err + } + defer rows.Close() + + var data []ServerData + for rows.Next() { + var sd ServerData + if err := rows.Scan(&sd.Time, &sd.IsOnline, &sd.Tps, &sd.OnlinePlayer, &sd.MaxPlayer); err != nil { + return nil, err + } + data = append(data, sd) + } + reverse(data) + return data, nil +} + +func reverse(slice []ServerData) { + last := len(slice) - 1 + for i := 0; i < len(slice)/2; i++ { + slice[i], slice[last-i] = slice[last-i], slice[i] + } +} + +func APIHandler(w http.ResponseWriter, r *http.Request) { + // 设置响应内容类型为JSON + w.Header().Set("Content-Type", "application/json") + + // 解析请求参数 + queryParams := r.URL.Query() + requestType := queryParams.Get("type") + + // 检查请求类型是否为 server_info + switch requestType { + case "server_info": + // 准备要返回的数据 + serverInfo := ServerInfo{ + ServerName: GlobalConfig.ServerInfo.Name, + ServerAddress: GlobalConfig.ServerInfo.Address, + ServerWebsite: GlobalConfig.ServerInfo.Website, + ServerDescription: GlobalConfig.ServerInfo.Description, + } + + // 创建响应结构 + response := struct { + Code int `json:"code"` + Data ServerInfo `json:"data"` + }{ + Code: 200, + Data: serverInfo, + } + + // 将响应结构序列化为JSON并写入响应体 + if err := json.NewEncoder(w).Encode(response); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + case "detailed_info": + clientTimeFormat := "2006/01/02 15:04:05" + t, err := time.Parse(clientTimeFormat, queryParams.Get("time")) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + dbTime := t.Format("2006-01-02 15:04:05") + + localDB, err := sql.Open("sqlite", "data/history.db") + if err != nil { + log.Fatal(err) + } + defer localDB.Close() + query := `SELECT time_index, online, tps, online_player, max_player, player_list + FROM data WHERE time_index > ? ORDER BY time_index ASC` + rows, err := localDB.Query(query, dbTime) + if err != nil { + return + } + defer rows.Close() + + var data []DetailedInfo + for rows.Next() { + var sd DetailedInfo + var playerList string + if err := rows.Scan(&sd.Time, &sd.IsOnline, &sd.Tps, &sd.OnlinePlayer, &sd.MaxPlayer, &playerList); err != nil { + return + } + sd.PlayerList = playerList + data = append(data, sd) + } + // 创建响应结构 + response := struct { + Code int `json:"code"` + Data DetailedInfo `json:"data"` + }{ + Code: 200, + Data: data[0], + } + + // 将响应结构序列化为JSON并写入响应体 + if err := json.NewEncoder(w).Encode(response); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + default: + http.Error(w, "Invalid request type", http.StatusBadRequest) + } +} + +func callback(data string) { + // log.Println("[DEBUG] Receive callback data: " + data) + + var jsonData map[string]interface{} + err := json.Unmarshal([]byte(data), &jsonData) + if err != nil { + log.Fatalln(err) + } + + switch toInt(jsonData["type"]) { + case rcon.DataType_connection_success: + isOnline = true + log.Println("[INFO] RCON connection success") + case rcon.DataType_connection_error: + isOnline = false + tps, tps5, tps15, onlinePlayer, maxPlayer, playerList = 0, 0, 0, 0, 0, []string{} + log.Println("[ERROR] RCON connection error") + case rcon.DataType_execution_error: + isOnline = false + tps, tps5, tps15, onlinePlayer, maxPlayer, playerList = 0, 0, 0, 0, 0, []string{} + log.Println("[ERROR] RCON execution error") + case rcon.DataType_data_tps: + log.Println("[DEBUG] TPS: " + strconv.FormatFloat(jsonData["data"].(map[string]interface{})["l1m"].(float64), 'f', -1, 64)) + tps = jsonData["data"].(map[string]interface{})["l1m"].(float64) + tps5 = jsonData["data"].(map[string]interface{})["l5m"].(float64) + tps15 = jsonData["data"].(map[string]interface{})["l15m"].(float64) + case rcon.DataType_data_list: + log.Println("[DEBUG] Player online: " + strconv.FormatFloat(jsonData["data"].(map[string]interface{})["online_player"].(float64), 'f', -1, 64) + "/" + strconv.FormatFloat(jsonData["data"].(map[string]interface{})["max_player"].(float64), 'f', -1, 64)) + onlinePlayer = int(jsonData["data"].(map[string]interface{})["online_player"].(float64)) + maxPlayer = int(jsonData["data"].(map[string]interface{})["max_player"].(float64)) + //read player list + playerList = []string{} + for _, player := range jsonData["data"].(map[string]interface{})["player_list"].([]interface{}) { + playerList = append(playerList, player.(string)) + } + } +} diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..b4913b3 --- /dev/null +++ b/config.yml @@ -0,0 +1,27 @@ +web: + host: "localhost" + port: 25565 + +rcon: + host: "localhost" + port: 25575 + password: "password" + +server_info: + name: "Demo" + address: "demo.meowdream.cn" + website: "https://uptimeow.meowdream.cn" + description: "Just a demo :)" + +warn: + enabled: true + dingtalkBot: + enabled: true + accessToken: "xxx" + secret: "xxx" + atMobile: "xxx" + enabledType: + lowTps: + enabled: true + threshold: 19.0 + offline: true \ No newline at end of file diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..d6e344f --- /dev/null +++ b/config/config.go @@ -0,0 +1,75 @@ +package config + +import ( + "gopkg.in/yaml.v3" + "log" + "os" + "sync" +) + +type ConfigData struct { + Web struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + } `yaml:"web"` + Rcon struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + Password string `yaml:"password"` + } `yaml:"rcon"` + ServerInfo struct { + Name string `yaml:"name"` + Address string `yaml:"address"` + Website string `yaml:"website"` + Description string `yaml:"description"` + } `yaml:"server_info"` + Warn struct { + Enabled bool `yaml:"enabled"` + DingTalkBot struct { + Enabled bool `yaml:"enabled"` + AccessToken string `yaml:"accessToken"` + Secret string `yaml:"secret"` + AtMobile string `yaml:"atMobile"` + } `yaml:"dingtalkBot"` + EnabledType struct { + LowTps struct { + Enabled bool `yaml:"enabled"` + Threold float64 `yaml:"threshold"` + } `yaml:"lowTps"` + Offline bool `yaml:"offline"` + } `yaml:"enabledType"` + } +} + +var config ConfigData +var once sync.Once + +func Load() ConfigData { + once.Do(func() { + // 读取YAML文件 + data, err := os.ReadFile("config.yml") + if err != nil { + log.Fatalln("Error reading YAML file:", err) + return + } + + // 解析YAML数据到config结构体 + err = yaml.Unmarshal(data, &config) + if err != nil { + log.Fatalln("Error parsing YAML data:", err) + return + } + + // 设置缺省值 + if config.Web.Port == 0 { + log.Println("Port not defined in config, using 80 as default...") + config.Web.Port = 80 // 默认端口 + } + if config.Web.Host == "" { + log.Println("Host not defined in config, using 0.0.0.0 as default...") + config.Web.Host = "0.0.0.0" // 默认主机 + } + }) + + return config +} diff --git a/data/history.db b/data/history.db new file mode 100644 index 0000000..7a4e29a Binary files /dev/null and b/data/history.db differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..dd2e84e --- /dev/null +++ b/go.mod @@ -0,0 +1,26 @@ +module github.com/MeowLynxSea/Uptimeow + +go 1.23.1 + +require ( + github.com/Tnze/go-mc v1.18.2 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/glebarez/sqlite v1.11.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/jinzhu/gorm v1.9.16 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/wanghuiyt/ding v0.0.2 // indirect + golang.org/x/sys v0.7.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/gorm v1.25.7 // indirect + modernc.org/libc v1.22.5 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/sqlite v1.23.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..37d6972 --- /dev/null +++ b/go.sum @@ -0,0 +1,67 @@ +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/Tnze/go-mc v1.18.2 h1:75dTJ0dJNI4V/7iG7Ze1pWkDmE9D02OQe6RfEZV0FBE= +github.com/Tnze/go-mc v1.18.2/go.mod h1:DyB0mWjox4fSiOdShzh7yx4nzx7q6AXUKzlXnT+iCTo= +github.com/Tnze/go-mc v1.20.2 h1:arHCE/WxLCxY73C/4ZNLdOymRYtdwoXE05ohB7HVN6Q= +github.com/Tnze/go-mc v1.20.2/go.mod h1:geoRj2HsXSkB3FJBuhr7wCzXegRlzWsVXd7h7jiJ6aQ= +github.com/Tnze/go-mc v1.20.3-0.20240907175330-9a1f5431370e h1:1kpZRcut6hymfTvCAZteOW4an/vwMyi9NNeDh0V/MDY= +github.com/Tnze/go-mc v1.20.3-0.20240907175330-9a1f5431370e/go.mod h1:vp949nHNUK5KVwSuadpN1vp7c1zvkPEniM08PQv1FcY= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/wanghuiyt/ding v0.0.2 h1:6ZISlgCSy6MVeaFR8kAdniALMRqd56GyO9LlmYdTw/s= +github.com/wanghuiyt/ding v0.0.2/go.mod h1:T1vPz74YMmGCBVKZzVsen/YAYRZ2bvBYXldUyD7Y4vc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= +modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= diff --git a/internal/bot/bot.go b/internal/bot/bot.go new file mode 100644 index 0000000..97f2477 --- /dev/null +++ b/internal/bot/bot.go @@ -0,0 +1,40 @@ +package bot + +import ( + "github.com/Tnze/go-mc/bot" + "github.com/Tnze/go-mc/bot/basic" + + // "encoding/json" + "errors" + "log" +) + +var ( + client *bot.Client + player *basic.Player +) + +func InitBot(callback func(data string)) { + client = bot.NewClient() + + player = basic.NewPlayer(client, basic.DefaultSettings) + + err := client.JoinServer("localhost:25565") + if err != nil { + log.Fatal(err) + } + + log.Println("Login success") + + var perr bot.PacketHandlerError + for { + if err = client.HandleGame(); err == nil { + panic("HandleGame never return nil") + } + if errors.As(err, &perr) { + log.Print(perr) + } else { + log.Fatal(err) + } + } +} diff --git a/internal/rcon/rcon.go b/internal/rcon/rcon.go new file mode 100644 index 0000000..371074e --- /dev/null +++ b/internal/rcon/rcon.go @@ -0,0 +1,269 @@ +package rcon + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "errors" + "github.com/MeowLynxSea/Uptimeow/config" + "github.com/robfig/cron/v3" + "log" + "net" + "regexp" + "strconv" + "time" +) + +type Connection struct { + conn net.Conn + pass string + addr string +} + +const ( + DataType_data_list = iota + DataType_data_tps + DataType_connection_error + DataType_connection_success + DataType_execution_error +) + +var GlobalConfig config.ConfigData +var Cron = cron.New() +var isRunning bool + +func InitRcon(callback func(data string)) { + + GlobalConfig = config.Load() + + var conn *Connection + + Cron.AddFunc("@every 5s", func() { + command := [...]string{"list", "tps"} + for _, v := range command { + response, err := conn.SendCommand(v) + if err != nil { + // log.Println("[ERROR] Error executing command:", err) + callback("{\"type\": " + strconv.Itoa(DataType_execution_error) + ", \"data\": \"Error executing command: " + err.Error() + "\"}") + isRunning = false + break + } else { + switch v { + case "list": + // 编译正则表达式 + onlinePlayerRegexp := regexp.MustCompile(`(\d+) of a max of (\d+) players online`) + playerListRegexp := regexp.MustCompile(`online: ([^:]+)`) + + // 使用正则表达式提取信息 + onlinePlayerMatches := onlinePlayerRegexp.FindStringSubmatch(response) + playerListMatches := playerListRegexp.FindStringSubmatch(response) + + // 检查是否匹配成功 + if onlinePlayerMatches != nil { + // 提取online_player和max_player + onlinePlayer, _ := strconv.Atoi(onlinePlayerMatches[1]) + maxPlayer, _ := strconv.Atoi(onlinePlayerMatches[2]) + + var playerList string + if playerListMatches != nil { + playerList = playerListMatches[1] + } + + // 创建一个结构体来保存数据 + type PlayerData struct { + OnlinePlayer int `json:"online_player"` + MaxPlayer int `json:"max_player"` + PlayerList []string `json:"player_list"` + } + + // 将玩家名字字符串分割成列表 + playerNames := regexp.MustCompile(`, `).Split(playerList, -1) + + // 实例化结构体并填充数据 + data := PlayerData{ + OnlinePlayer: onlinePlayer, + MaxPlayer: maxPlayer, + PlayerList: playerNames, + } + + // 将结构体格式化为JSON + jsonData, err := json.MarshalIndent(data, "", " ") + if err != nil { + log.Println("[ERROR] Error marshalling JSON:", err) + } + + callback("{\"type\": " + strconv.Itoa(DataType_data_list) + ", \"data\": " + string(jsonData) + "}") + } else { + log.Println("[ERROR] Could not extract the required information.") + isRunning = false + callback("{\"type\": " + strconv.Itoa(DataType_execution_error) + "}") + break + } + case "tps": + re := regexp.MustCompile(`§[a-zA-Z](\d+\.\d+|\d+)`) + matches := re.FindAllStringSubmatch(response, -1) + var numbers []string + if len(matches) != 3 { + isRunning = false + callback("{\"type\": " + strconv.Itoa(DataType_execution_error) + "}") + break + } + for _, match := range matches { + if len(match) > 1 { + numbers = append(numbers, match[1]) + } + } + callback(`{ + "type": ` + strconv.Itoa(DataType_data_tps) + `, + "data": { + "l1m": ` + numbers[0] + `, + "l5m": ` + numbers[1] + `, + "l15m": ` + numbers[2] + ` + } + }`) + } + } + } + }) + + for { + isRunning = true + log.Println("[INFO] Connecting to RCON server " + GlobalConfig.Rcon.Host + ":" + strconv.Itoa(GlobalConfig.Rcon.Port) + "...") + + var err error + conn, err = NewConnection(GlobalConfig.Rcon.Host+":"+strconv.Itoa(GlobalConfig.Rcon.Port), GlobalConfig.Rcon.Password) + if err != nil { + callback("{\"type\": " + strconv.Itoa(DataType_connection_error) + ", \"data\": \"Error connecting to RCON server: " + err.Error() + "\"}") + isRunning = false + } + + if isRunning { + callback("{\"type\": " + strconv.Itoa(DataType_connection_success) + "}") + Cron.Start() + } + + for isRunning { + time.Sleep(10 * time.Nanosecond) + } + + Cron.Stop() + + log.Println("[INFO] RCON server has disconnected. Trying to reconnect in 1 seconds...") + time.Sleep(3 * time.Second) + } +} + +var uniqueID int32 = 0 + +func NewConnection(addr, pass string) (*Connection, error) { + uniqueID++ + conn, err := net.Dial("tcp", addr) + if err != nil { + return nil, err + } + c := &Connection{conn: conn, pass: pass, addr: addr} + if err := c.auth(); err != nil { + return nil, err + } + return c, nil +} + +func (c *Connection) SendCommand(cmd string) (string, error) { + err := c.sendCommand(2, []byte(cmd)) + if err != nil { + return "", err + } + pkg, err := c.readPkg() + if err != nil { + return "", err + } + return string(pkg.Body), err +} + +func (c *Connection) auth() error { + c.sendCommand(3, []byte(c.pass)) + pkg, err := c.readPkg() + if err != nil { + return err + } + + if pkg.Type != 2 || pkg.ID != uniqueID { + return errors.New("incorrect password") + } + + return nil +} + +func (c *Connection) sendCommand(typ int32, body []byte) error { + size := int32(4 + 4 + len(body) + 2) + uniqueID += 1 + id := uniqueID + + wtr := binaryReadWriter{ByteOrder: binary.LittleEndian} + wtr.Write(size) + wtr.Write(id) + wtr.Write(typ) + wtr.Write(body) + wtr.Write([]byte{0x0, 0x0}) + if wtr.err != nil { + return wtr.err + } + + c.conn.Write(wtr.buf.Bytes()) + return nil +} + +func (c *Connection) readPkg() (pkg, error) { + const bufSize = 4096 + b := make([]byte, bufSize) + + // Doesn't handle split messages correctly. + read, err := c.conn.Read(b) + if err != nil { + return pkg{}, err + } + + p := pkg{} + rdr := binaryReadWriter{ByteOrder: binary.LittleEndian, + buf: bytes.NewBuffer(b)} + rdr.Read(&p.Size) + rdr.Read(&p.ID) + rdr.Read(&p.Type) + body := [bufSize - 12]byte{} + rdr.Read(&body) + if rdr.err != nil { + return p, rdr.err + } + p.Body = body[:read-14] + return p, nil +} + +type pkg struct { + Size int32 + ID int32 + Type int32 + Body []byte +} + +type binaryReadWriter struct { + ByteOrder binary.ByteOrder + err error + buf *bytes.Buffer +} + +func (b *binaryReadWriter) Write(v interface{}) { + if b.err != nil { + return + } + if b.buf == nil { + b.buf = new(bytes.Buffer) + } + b.err = binary.Write(b.buf, b.ByteOrder, v) +} + +func (b *binaryReadWriter) Read(v interface{}) { + if b.err != nil || b.buf == nil { + return + } + b.err = binary.Read(b.buf, b.ByteOrder, v) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..b3dc8b6 --- /dev/null +++ b/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "log" + "net/http" + "strconv" + + "github.com/MeowLynxSea/Uptimeow/api" + "github.com/MeowLynxSea/Uptimeow/config" + "github.com/MeowLynxSea/Uptimeow/web" +) + +var GlobalConfig config.ConfigData + +func main() { + GlobalConfig = config.Load() + + http.HandleFunc("/ws", api.WebSocketHandler) + http.HandleFunc("/api/", api.APIHandler) + http.HandleFunc("/", web.IndexHandler) + + log.Println("[INFO] Starting server on " + GlobalConfig.Web.Host + ":" + strconv.Itoa(GlobalConfig.Web.Port) + "...") + if err := http.ListenAndServe(GlobalConfig.Web.Host+":"+strconv.Itoa(GlobalConfig.Web.Port), nil); err != nil { + panic(err) + } +} diff --git a/public/.preload b/public/.preload new file mode 100644 index 0000000..3f13b42 --- /dev/null +++ b/public/.preload @@ -0,0 +1,66 @@ +local su=require"sqlutil" -- Load SQL utility library + +local dbname=ba.openio"home":realpath"/data/database.sqlite.db" + +login = {} + +if not su.exist(dbname) then + -- Create a database environment object and open data/file.sqlite.db + local env,conn = su.open"database" + -- 创建一个数据库,其中包含用户的mail,name,password,created_at,salt,balance,is_admin + trace("Creating DB...") + conn:execute"CREATE TABLE IF NOT EXISTS users (token TEXT PRIMARY KEY,name TEXT,avatar_url TEXT,balance INTEGER);" + conn:execute"CREATE TABLE IF NOT EXISTS histroy (token TEXT PRIMARY KEY,action_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,delta INTEGER,type TEXT,balance INTEGER,total_amount INTEGER,show_amount INTEGER,remark TEXT,custom_order_id TEXT,out_trade_no TEXT,user_id TEXT);" + conn:execute"CREATE INDEX IF NOT EXISTS idx_history_token ON histroy(token);" + conn:execute"CREATE INDEX IF NOT EXISTS idx_history_action_time ON histroy(action_time);" + trace("DB created") + su.close(env,conn) + else + trace("DB already exists") + end + +local env = luasql.sqlite() +local conn = assert(env:connect(dbname)) -- DB connection used for write operations +assert(conn:setautocommit"EXCLUSIVE") -- EXCLUSIVE for first DB operation only +conn:setbusytimeout(2000) +function onunload() -- auto run when app terminates + trace"Closing DB" + conn:close() + env:close() +end + +-- Function used for committing and preparing next transaction for EXCLUSIVE use. +-- The function is used exclusively by dbexec below. +local function commit() + while true do + local ok, err = conn:commit"IMMEDIATE" -- Commit and prepare for IMMEDIATE transaction type + if ok then break end + if err ~= "BUSY" then + trace("ERROR: commit failed on exclusive connection:",err) + break + end + trace"BUSY writing, but we will try again" + end +end +commit() -- the two conn:exec above (in EXCLUSIVE mode) + +-- Create the thread and the function used for inserting callback +-- functions into the thread queue. +local dbthread=ba.thread.create() +function dbexec(doit) -- used by index.lsp + dbthread:run(doit) -- queue the doit function in index.lsp + dbthread:run(commit) -- queue commit +end + +-- Opens/creates a new DB 'read' connection object -- i.e. should only +-- be used for read operations +function openconn() + local conn = assert(env:connect(dbname)) + if conn then conn:setbusytimeout(2000) end + return conn,env +end + +-- Returns our persistent DB write connection object +function getexconn() + return conn,env +end \ No newline at end of file diff --git a/public/.vscode/settings.json b/public/.vscode/settings.json new file mode 100644 index 0000000..879feb6 --- /dev/null +++ b/public/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "Codegeex.RepoIndex": true, + "Codegeex.CommitMessageStyle": "Default" +} \ No newline at end of file diff --git a/public/LICENSE b/public/LICENSE new file mode 100644 index 0000000..a07b777 --- /dev/null +++ b/public/LICENSE @@ -0,0 +1,102 @@ +Reciprocal Public License 1.5 (RPL1.5) + +Version 1.5, July 15, 2007 + +Copyright (C) 2001-2007 Technical Pursuit Inc., All Rights Reserved. + +PREAMBLE + +The Reciprocal Public License (RPL) is based on the concept of reciprocity or, if you prefer, fairness. + +In short, this license grew out of a desire to close loopholes in previous open source licenses, loopholes that allowed parties to acquire open source software and derive financial benefit from it without having to release their improvements or derivatives to the community which enabled them. This occurred any time an entity did not release their application to a "third party". + +While there is a certain freedom in this model of licensing, it struck the authors of the RPL as being unfair to the open source community at large and to the original authors of the works in particular. After all, bug fixes, extensions, and meaningful and valuable derivatives were not consistently finding their way back into the community where they could fuel further, and faster, growth and expansion of the overall open source software base. + +While you should clearly read and understand the entire license, the essence of the RPL is found in two definitions: "Deploy" and "Required Components". + +Regarding deployment, under the RPL your changes, bug fixes, extensions, etc. must be made available to the open source community at large when you Deploy in any form -- either internally or to an outside party. Once you start running the software you have to start sharing the software. + +Further, under the RPL all components you author including schemas, scripts, source code, etc. -- regardless of whether they're compiled into a single binary or used as two halves of client/server application -- must be shared. You have to share the whole pie, not an isolated slice of it. + +In addition to these goals, the RPL was authored to meet the requirements of the Open Source Definition as maintained by the Open Source Initiative (OSI). + +The specific terms and conditions of the license are defined in the remainder of this document. + +LICENSE TERMS + +1.0 General; Applicability & Definitions. This Reciprocal Public License Version 1.5 ("License") applies to any programs or other works as well as any and all updates or maintenance releases of said programs or works ("Software") not already covered by this License which the Software copyright holder ("Licensor") makes available containing a License Notice (hereinafter defined) from the Licensor specifying or allowing use or distribution under the terms of this License. As used in this License: +1.1 "Contributor" means any person or entity who created or contributed to the creation of an Extension. +1.2 "Deploy" means to use, Serve, sublicense or distribute Licensed Software other than for Your internal Research and/or Personal Use, and includes without limitation, any and all internal use or distribution of Licensed Software within Your business or organization other than for Research and/or Personal Use, as well as direct or indirect sublicensing or distribution of Licensed Software by You to any third party in any form or manner. +1.3 "Derivative Works" as used in this License is defined under U.S. copyright law. +1.4 "Electronic Distribution Mechanism" means a mechanism generally accepted in the software development community for the electronic transfer of data such as download from an FTP server or web site, where such mechanism is publicly accessible. +1.5 "Extensions" means any Modifications, Derivative Works, or Required Components as those terms are defined in this License. +1.6 "License" means this Reciprocal Public License. +1.7 "License Notice" means any notice contained in EXHIBIT A. +1.8 "Licensed Software" means any Software licensed pursuant to this License. Licensed Software also includes all previous Extensions from any Contributor that You receive. +1.9 "Licensor" means the copyright holder of any Software previously not covered by this License who releases the Software under the terms of this License. +1.10 "Modifications" means any additions to or deletions from the substance or structure of (i) a file or other storage containing Licensed Software, or (ii) any new file or storage that contains any part of Licensed Software, or (iii) any file or storage which replaces or otherwise alters the original functionality of Licensed Software at runtime. +1.11 "Personal Use" means use of Licensed Software by an individual solely for his or her personal, private and non-commercial purposes. An individual's use of Licensed Software in his or her capacity as an officer, employee, member, independent contractor or agent of a corporation, business or organization (commercial or non-commercial) does not qualify as Personal Use. +1.12 "Required Components" means any text, programs, scripts, schema, interface definitions, control files, or other works created by You which are required by a third party of average skill to successfully install and run Licensed Software containing Your Modifications, or to install and run Your Derivative Works. +1.13 "Research" means investigation or experimentation for the purpose of understanding the nature and limits of the Licensed Software and its potential uses. +1.14 "Serve" means to deliver Licensed Software and/or Your Extensions by means of a computer network to one or more computers for purposes of execution of Licensed Software and/or Your Extensions. +1.15 "Software" means any computer programs or other works as well as any updates or maintenance releases of those programs or works which are distributed publicly by Licensor. +1.16 "Source Code" means the preferred form for making modifications to the Licensed Software and/or Your Extensions, including all modules contained therein, plus any associated text, interface definition files, scripts used to control compilation and installation of an executable program or other components required by a third party of average skill to build a running version of the Licensed Software or Your Extensions. +1.17 "User-Visible Attribution Notice" means any notice contained in EXHIBIT B. +1.18 "You" or "Your" means an individual or a legal entity exercising rights under this License. For legal entities, "You" or "Your" includes any entity which controls, is controlled by, or is under common control with, You, where "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of fifty percent (50%) or more of the outstanding shares or beneficial ownership of such entity. +2.0 Acceptance Of License. You are not required to accept this License since you have not signed it, however nothing else grants you permission to use, copy, distribute, modify, or create derivatives of either the Software or any Extensions created by a Contributor. These actions are prohibited by law if you do not accept this License. Therefore, by performing any of these actions You indicate Your acceptance of this License and Your agreement to be bound by all its terms and conditions. IF YOU DO NOT AGREE WITH ALL THE TERMS AND CONDITIONS OF THIS LICENSE DO NOT USE, MODIFY, CREATE DERIVATIVES, OR DISTRIBUTE THE SOFTWARE. IF IT IS IMPOSSIBLE FOR YOU TO COMPLY WITH ALL THE TERMS AND CONDITIONS OF THIS LICENSE THEN YOU CAN NOT USE, MODIFY, CREATE DERIVATIVES, OR DISTRIBUTE THE SOFTWARE. +3.0 Grant of License From Licensor. Subject to the terms and conditions of this License, Licensor hereby grants You a world-wide, royalty-free, non-exclusive license, subject to Licensor's intellectual property rights, and any third party intellectual property claims derived from the Licensed Software under this License, to do the following: +3.1 Use, reproduce, modify, display, perform, sublicense and distribute Licensed Software and Your Extensions in both Source Code form or as an executable program. +3.2 Create Derivative Works (as that term is defined under U.S. copyright law) of Licensed Software by adding to or deleting from the substance or structure of said Licensed Software. +3.3 Under claims of patents now or hereafter owned or controlled by Licensor, to make, use, have made, and/or otherwise dispose of Licensed Software or portions thereof, but solely to the extent that any such claim is necessary to enable You to make, use, have made, and/or otherwise dispose of Licensed Software or portions thereof. +3.4 Licensor reserves the right to release new versions of the Software with different features, specifications, capabilities, functions, licensing terms, general availability or other characteristics. Title, ownership rights, and intellectual property rights in and to the Licensed Software shall remain in Licensor and/or its Contributors. +4.0 Grant of License From Contributor. By application of the provisions in Section 6 below, each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license, subject to said Contributor's intellectual property rights, and any third party intellectual property claims derived from the Licensed Software under this License, to do the following: +4.1 Use, reproduce, modify, display, perform, sublicense and distribute any Extensions Deployed by such Contributor or portions thereof, in both Source Code form or as an executable program, either on an unmodified basis or as part of Derivative Works. +4.2 Under claims of patents now or hereafter owned or controlled by Contributor, to make, use, have made, and/or otherwise dispose of Extensions or portions thereof, but solely to the extent that any such claim is necessary to enable You to make, use, have made, and/or otherwise dispose of Licensed Software or portions thereof. +5.0 Exclusions From License Grant. Nothing in this License shall be deemed to grant any rights to trademarks, copyrights, patents, trade secrets or any other intellectual property of Licensor or any Contributor except as expressly stated herein. Except as expressly stated in Sections 3 and 4, no other patent rights, express or implied, are granted herein. Your Extensions may require additional patent licenses from Licensor or Contributors which each may grant in its sole discretion. No right is granted to the trademarks of Licensor or any Contributor even if such marks are included in the Licensed Software. Nothing in this License shall be interpreted to prohibit Licensor from licensing under different terms from this License any code that Licensor otherwise would have a right to license. +5.1 You expressly acknowledge and agree that although Licensor and each Contributor grants the licenses to their respective portions of the Licensed Software set forth herein, no assurances are provided by Licensor or any Contributor that the Licensed Software does not infringe the patent or other intellectual property rights of any other entity. Licensor and each Contributor disclaim any liability to You for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, You hereby assume sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow You to distribute the Licensed Software, it is Your responsibility to acquire that license before distributing the Licensed Software. +6.0 Your Obligations And Grants. In consideration of, and as an express condition to, the licenses granted to You under this License You hereby agree that any Modifications, Derivative Works, or Required Components (collectively Extensions) that You create or to which You contribute are governed by the terms of this License including, without limitation, Section 4. Any Extensions that You create or to which You contribute must be Deployed under the terms of this License or a future version of this License released under Section 7. You hereby grant to Licensor and all third parties a world-wide, non-exclusive, royalty-free license under those intellectual property rights You own or control to use, reproduce, display, perform, modify, create derivatives, sublicense, and distribute Licensed Software, in any form. Any Extensions You make and Deploy must have a distinct title so as to readily tell any subsequent user or Contributor that the Extensions are by You. You must include a copy of this License or directions on how to obtain a copy with every copy of the Extensions You distribute. You agree not to offer or impose any terms on any Source Code or executable version of the Licensed Software, or its Extensions that alter or restrict the applicable version of this License or the recipients' rights hereunder. +6.1 Availability of Source Code. You must make available, under the terms of this License, the Source Code of any Extensions that You Deploy, via an Electronic Distribution Mechanism. The Source Code for any version that You Deploy must be made available within one (1) month of when you Deploy and must remain available for no less than twelve (12) months after the date You cease to Deploy. You are responsible for ensuring that the Source Code to each version You Deploy remains available even if the Electronic Distribution Mechanism is maintained by a third party. You may not charge a fee for any copy of the Source Code distributed under this Section in excess of Your actual cost of duplication and distribution of said copy. +6.2 Description of Modifications. You must cause any Modifications that You create or to which You contribute to be documented in the Source Code, clearly describing the additions, changes or deletions You made. You must include a prominent statement that the Modifications are derived, directly or indirectly, from the Licensed Software and include the names of the Licensor and any Contributor to the Licensed Software in (i) the Source Code and (ii) in any notice displayed by the Licensed Software You distribute or in related documentation in which You describe the origin or ownership of the Licensed Software. You may not modify or delete any pre-existing copyright notices, change notices or License text in the Licensed Software without written permission of the respective Licensor or Contributor. +6.3 Intellectual Property Matters. +a. Third Party Claims. If You have knowledge that a license to a third party's intellectual property right is required to exercise the rights granted by this License, You must include a human-readable file with Your distribution that describes the claim and the party making the claim in sufficient detail that a recipient will know whom to contact. +b. Contributor APIs. If Your Extensions include an application programming interface ("API") and You have knowledge of patent licenses that are reasonably necessary to implement that API, You must also include this information in a human-readable file supplied with Your distribution. +c. Representations. You represent that, except as disclosed pursuant to 6.3(a) above, You believe that any Extensions You distribute are Your original creations and that You have sufficient rights to grant the rights conveyed by this License. +6.4 Required Notices. +a. License Text. You must duplicate this License or instructions on how to acquire a copy in any documentation You provide along with the Source Code of any Extensions You create or to which You contribute, wherever You describe recipients' rights relating to Licensed Software. +b. License Notice. You must duplicate any notice contained in EXHIBIT A (the "License Notice") in each file of the Source Code of any copy You distribute of the Licensed Software and Your Extensions. If You create an Extension, You may add Your name as a Contributor to the Source Code and accompanying documentation along with a description of the contribution. If it is not possible to put the License Notice in a particular Source Code file due to its structure, then You must include such License Notice in a location where a user would be likely to look for such a notice. +c. Source Code Availability. You must notify the software community of the availability of Source Code to Your Extensions within one (1) month of the date You initially Deploy and include in such notification a description of the Extensions, and instructions on how to acquire the Source Code. Should such instructions change you must notify the software community of revised instructions within one (1) month of the date of change. You must provide notification by posting to appropriate news groups, mailing lists, weblogs, or other sites where a publicly accessible search engine would reasonably be expected to index your post in relationship to queries regarding the Licensed Software and/or Your Extensions. +d. User-Visible Attribution. You must duplicate any notice contained in EXHIBIT B (the "User-Visible Attribution Notice") in each user-visible display of the Licensed Software and Your Extensions which delineates copyright, ownership, or similar attribution information. If You create an Extension, You may add Your name as a Contributor, and add Your attribution notice, as an equally visible and functional element of any User-Visible Attribution Notice content. To ensure proper attribution, You must also include such User-Visible Attribution Notice in at least one location in the Software documentation where a user would be likely to look for such notice. +6.5 Additional Terms. You may choose to offer, and charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Licensed Software. However, You may do so only on Your own behalf, and not on behalf of the Licensor or any Contributor except as permitted under other agreements between you and Licensor or Contributor. You must make it clear that any such warranty, support, indemnity or liability obligation is offered by You alone, and You hereby agree to indemnify the Licensor and every Contributor for any liability plus attorney fees, costs, and related expenses due to any such action or claim incurred by the Licensor or such Contributor as a result of warranty, support, indemnity or liability terms You offer. +6.6 Conflicts With Other Licenses. Where any portion of Your Extensions, by virtue of being Derivative Works of another product or similar circumstance, fall under the terms of another license, the terms of that license should be honored however You must also make Your Extensions available under this License. If the terms of this License continue to conflict with the terms of the other license you may write the Licensor for permission to resolve the conflict in a fashion that remains consistent with the intent of this License. Such permission will be granted at the sole discretion of the Licensor. +7.0 Versions of This License. Licensor may publish from time to time revised versions of the License. Once Licensed Software has been published under a particular version of the License, You may always continue to use it under the terms of that version. You may also choose to use such Licensed Software under the terms of any subsequent version of the License published by Licensor. No one other than Licensor has the right to modify the terms applicable to Licensed Software created under this License. +7.1 If You create or use a modified version of this License, which You may do only in order to apply it to software that is not already Licensed Software under this License, You must rename Your license so that it is not confusingly similar to this License, and must make it clear that Your license contains terms that differ from this License. In so naming Your license, You may not use any trademark of Licensor or of any Contributor. Should Your modifications to this License be limited to alteration of a) Section 13.8 solely to modify the legal Jurisdiction or Venue for disputes, b) EXHIBIT A solely to define License Notice text, or c) to EXHIBIT B solely to define a User-Visible Attribution Notice, You may continue to refer to Your License as the Reciprocal Public License or simply the RPL. +8.0 Disclaimer of Warranty. LICENSED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE LICENSED SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. FURTHER THERE IS NO WARRANTY MADE AND ALL IMPLIED WARRANTIES ARE DISCLAIMED THAT THE LICENSED SOFTWARE MEETS OR COMPLIES WITH ANY DESCRIPTION OF PERFORMANCE OR OPERATION, SAID COMPATIBILITY AND SUITABILITY BEING YOUR RESPONSIBILITY. LICENSOR DISCLAIMS ANY WARRANTY, IMPLIED OR EXPRESSED, THAT ANY CONTRIBUTOR'S EXTENSIONS MEET ANY STANDARD OF COMPATIBILITY OR DESCRIPTION OF PERFORMANCE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LICENSED SOFTWARE IS WITH YOU. SHOULD LICENSED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (AND NOT THE LICENSOR OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. UNDER THE TERMS OF THIS LICENSOR WILL NOT SUPPORT THIS SOFTWARE AND IS UNDER NO OBLIGATION TO ISSUE UPDATES TO THIS SOFTWARE. LICENSOR HAS NO KNOWLEDGE OF ERRANT CODE OR VIRUS IN THIS SOFTWARE, BUT DOES NOT WARRANT THAT THE SOFTWARE IS FREE FROM SUCH ERRORS OR VIRUSES. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF LICENSED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. +9.0 Limitation of Liability. UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE LICENSOR, ANY CONTRIBUTOR, OR ANY DISTRIBUTOR OF LICENSED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. +10.0 High Risk Activities. THE LICENSED SOFTWARE IS NOT FAULT-TOLERANT AND IS NOT DESIGNED, MANUFACTURED, OR INTENDED FOR USE OR DISTRIBUTION AS ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT NAVIGATION OR COMMUNICATIONS SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE LICENSED SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). LICENSOR AND CONTRIBUTORS SPECIFICALLY DISCLAIM ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES. +11.0 Responsibility for Claims. As between Licensor and Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of its utilization of rights under this License which specifically disclaims warranties and limits any liability of the Licensor. This paragraph is to be used in conjunction with and controlled by the Disclaimer Of Warranties of Section 8, the Limitation Of Damages in Section 9, and the disclaimer against use for High Risk Activities in Section 10. The Licensor has thereby disclaimed all warranties and limited any damages that it is or may be liable for. You agree to work with Licensor and Contributors to distribute such responsibility on an equitable basis consistent with the terms of this License including Sections 8, 9, and 10. Nothing herein is intended or shall be deemed to constitute any admission of liability. +12.0 Termination. This License and all rights granted hereunder will terminate immediately in the event of the circumstances described in Section 13.6 or if applicable law prohibits or restricts You from fully and or specifically complying with Sections 3, 4 and/or 6, or prevents the enforceability of any of those Sections, and You must immediately discontinue any use of Licensed Software. +12.1 Automatic Termination Upon Breach. This License and the rights granted hereunder will terminate automatically if You fail to comply with the terms herein and fail to cure such breach within thirty (30) days of becoming aware of the breach. All sublicenses to the Licensed Software that are properly granted shall survive any termination of this License. Provisions that, by their nature, must remain in effect beyond the termination of this License, shall survive. +12.2 Termination Upon Assertion of Patent Infringement. If You initiate litigation by asserting a patent infringement claim (excluding declaratory judgment actions) against Licensor or a Contributor (Licensor or Contributor against whom You file such an action is referred to herein as "Respondent") alleging that Licensed Software directly or indirectly infringes any patent, then any and all rights granted by such Respondent to You under Sections 3 or 4 of this License shall terminate prospectively upon sixty (60) days notice from Respondent (the "Notice Period") unless within that Notice Period You either agree in writing (i) to pay Respondent a mutually agreeable reasonably royalty for Your past or future use of Licensed Software made by such Respondent, or (ii) withdraw Your litigation claim with respect to Licensed Software against such Respondent. If within said Notice Period a reasonable royalty and payment arrangement are not mutually agreed upon in writing by the parties or the litigation claim is not withdrawn, the rights granted by Licensor to You under Sections 3 and 4 automatically terminate at the expiration of said Notice Period. +12.3 Reasonable Value of This License. If You assert a patent infringement claim against Respondent alleging that Licensed Software directly or indirectly infringes any patent where such claim is resolved (such as by license or settlement) prior to the initiation of patent infringement litigation, then the reasonable value of the licenses granted by said Respondent under Sections 3 and 4 shall be taken into account in determining the amount or value of any payment or license. +12.4 No Retroactive Effect of Termination. In the event of termination under this Section all end user license agreements (excluding licenses to distributors and resellers) that have been validly granted by You or any distributor hereunder prior to termination shall survive termination. +13.0 Miscellaneous. +13.1 U.S. Government End Users. The Licensed Software is a "commercial item," as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer software" and "commercial computer software documentation," as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire Licensed Software with only those rights set forth herein. +13.2 Relationship of Parties. This License will not be construed as creating an agency, partnership, joint venture, or any other form of legal association between or among You, Licensor, or any Contributor, and You will not represent to the contrary, whether expressly, by implication, appearance, or otherwise. +13.3 Independent Development. Nothing in this License will impair Licensor's right to acquire, license, develop, subcontract, market, or distribute technology or products that perform the same or similar functions as, or otherwise compete with, Extensions that You may develop, produce, market, or distribute. +13.4 Consent To Breach Not Waiver. Failure by Licensor or Contributor to enforce any provision of this License will not be deemed a waiver of future enforcement of that or any other provision. +13.5 Severability. This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. +13.6 Inability to Comply Due to Statute or Regulation. If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Licensed Software due to statute, judicial order, or regulation, then You cannot use, modify, or distribute the software. +13.7 Export Restrictions. You may be restricted with respect to downloading or otherwise acquiring, exporting, or reexporting the Licensed Software or any underlying information or technology by United States and other applicable laws and regulations. By downloading or by otherwise obtaining the Licensed Software, You are agreeing to be responsible for compliance with all applicable laws and regulations. +13.8 Arbitration, Jurisdiction & Venue. This License shall be governed by Colorado law provisions (except to the extent applicable law, if any, provides otherwise), excluding its conflict-of-law provisions. You expressly agree that any dispute relating to this License shall be submitted to binding arbitration under the rules then prevailing of the American Arbitration Association. You further agree that Adams County, Colorado USA is proper venue and grant such arbitration proceeding jurisdiction as may be appropriate for purposes of resolving any dispute under this License. Judgement upon any award made in arbitration may be entered and enforced in any court of competent jurisdiction. The arbitrator shall award attorney's fees and costs of arbitration to the prevailing party. Should either party find it necessary to enforce its arbitration award or seek specific performance of such award in a civil court of competent jurisdiction, the prevailing party shall be entitled to reasonable attorney's fees and costs. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. You and Licensor expressly waive any rights to a jury trial in any litigation concerning Licensed Software or this License. Any law or regulation that provides that the language of a contract shall be construed against the drafter shall not apply to this License. +13.9 Entire Agreement. This License constitutes the entire agreement between the parties with respect to the subject matter hereof. +EXHIBIT A + +The License Notice below must appear in each file of the Source Code of any copy You distribute of the Licensed Software or any Extensions thereto: + +Unless explicitly acquired and licensed from Licensor under another license, the contents of this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent versions as allowed by the RPL, and You may not copy or use this file in either source code or executable form, except in compliance with the terms and conditions of the RPL. + +All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language governing rights and limitations under the RPL. + +EXHIBIT B + +The User-Visible Attribution Notice below, when provided, must appear in each user-visible display as defined in Section 6.4 (d): \ No newline at end of file diff --git a/public/README.md b/public/README.md new file mode 100644 index 0000000..d17e324 --- /dev/null +++ b/public/README.md @@ -0,0 +1 @@ +"# MeowPayment" diff --git a/public/about.html b/public/about.html new file mode 100644 index 0000000..ad09759 --- /dev/null +++ b/public/about.html @@ -0,0 +1,213 @@ + + + +
+ + + ++ 你说得对,但是Uptimeow是一款专为Minecraft服务器设计的实时状态监控面板。它构建于一个精密的网络架构之上,为服务器管理员提供了一个被称作“数据之窗”的透明界面。在这里,关键的服务器参数将被实时监控,如同被神选中的人获得“神之眼”一般,Uptimeow赋予了你洞察服务器运行状态的能力。你将扮演一位细心的管理员,在这个高效的监控平台中,邂逅各种图表和数据,它们各具特色,共同讲述着服务器的健康状况。与Uptimeow一起,你将轻松应对各种挑战,确保服务器稳定运行,找回玩家们流畅游戏体验的同时——逐步发掘服务器性能的真相。 +
++ Learn more » + Github » +
++ 通过Uptimeow,您可以在线监控服务器的运行状态,如是否在线、TPS、在线人数等 +
++ 您可以在线回溯过往的服务器状态记录,以便结合日志更好地排查问题 +
++ 通过将本面板开放给玩家,玩家可以通过配置的方式及时联系管理组以报告服务器故障 +
++ 您可以自定义配置消息推送服务,当服务器状态异常时,Uptimeow将会自动推送报警消息到您的手机,以便及时解决问题 +
++ 通过消息推送服务,您也可以将服务器信息汇总推送至服务器管理/技术,以便优化游戏体验 +
+Uptimeow的部署非常简单。
+ 1. 修改服务器配置,启用RCON,记下端口和密码
+ 2. 下载Release版本,修改配置文件
+ 3. 启动服务,访问面板
+
欢迎向我们提交Uptimeow的错误,或对Uptimeow提出您的宝贵意见,您的反馈是我们前进的动力!
+`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\n\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n\n// Abbreviations\n//\n// 1. Add the correct text decoration in Chrome, Edge, Opera, and Safari.\n// 2. Add explicit cursor to indicate changed behavior.\n// 3. Prevent the text-decoration to be skipped.\n\nabbr[title] {\n text-decoration: underline dotted; // 1\n cursor: help; // 2\n text-decoration-skip-ink: none; // 3\n}\n\n\n// Address\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\n\n// Lists\n\nol,\nul {\n padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\n// 1. Undo browser default\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // 1\n}\n\n\n// Blockquote\n\nblockquote {\n margin: 0 0 1rem;\n}\n\n\n// Strong\n//\n// Add the correct font weight in Chrome, Edge, and Safari\n\nb,\nstrong {\n font-weight: $font-weight-bolder;\n}\n\n\n// Small\n//\n// Add the correct font size in all browsers\n\nsmall {\n @include font-size($small-font-size);\n}\n\n\n// Mark\n\nmark {\n padding: $mark-padding;\n color: var(--#{$prefix}highlight-color);\n background-color: var(--#{$prefix}highlight-bg);\n}\n\n\n// Sub and Sup\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n\nsub,\nsup {\n position: relative;\n @include font-size($sub-sup-font-size);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n// Links\n\na {\n color: rgba(var(--#{$prefix}link-color-rgb), var(--#{$prefix}link-opacity, 1));\n text-decoration: $link-decoration;\n\n &:hover {\n --#{$prefix}link-color-rgb: var(--#{$prefix}link-hover-color-rgb);\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([class]) {\n &,\n &:hover {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n// Code\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-code;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n}\n\n// 1. Remove browser default top margin\n// 2. Reset browser default of `1em` to use `rem`s\n// 3. Don't allow content to break outside\n\npre {\n display: block;\n margin-top: 0; // 1\n margin-bottom: 1rem; // 2\n overflow: auto; // 3\n @include font-size($code-font-size);\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n @include font-size(inherit);\n color: inherit;\n word-break: normal;\n }\n}\n\ncode {\n @include font-size($code-font-size);\n color: var(--#{$prefix}code-color);\n word-wrap: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n @include font-size($kbd-font-size);\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n\n kbd {\n padding: 0;\n @include font-size(1em);\n font-weight: $nested-kbd-font-weight;\n }\n}\n\n\n// Figures\n//\n// Apply a consistent margin strategy (matches our type styles).\n\nfigure {\n margin: 0 0 1rem;\n}\n\n\n// Images and content\n\nimg,\nsvg {\n vertical-align: middle;\n}\n\n\n// Tables\n//\n// Prevent double borders\n\ntable {\n caption-side: bottom;\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: $table-cell-padding-y;\n padding-bottom: $table-cell-padding-y;\n color: $table-caption-color;\n text-align: left;\n}\n\n// 1. Removes font-weight bold by inheriting\n// 2. Matches default `