package pages import ( "os" "os/exec" "regexp" "runtime" "sort" "strconv" "strings" log "log/slog" "github.com/ProjectSegfault/publapi/utils" "github.com/gofiber/fiber/v2" "github.com/spf13/viper" ) type UserStruct struct { Status int `json:"status"` Online int `json:"online"` Total int `json:"total"` Users []UserInfo `json:"users"` } type UserInfo struct { Name string `json:"name"` FullName string `json:"fullName"` Desc string `json:"desc"` Online bool `json:"online"` Op bool `json:"op"` Created int `json:"created"` Email string `json:"email"` Matrix string `json:"matrix"` Fediverse string `json:"fediverse"` Website string `json:"website"` Capsule string `json:"capsule"` Loc string `json:"loc"` } type ByAdminAndName []UserInfo func (a ByAdminAndName) Len() int { return len(a) } func (a ByAdminAndName) Less(i, j int) bool { // If one person is an admin and the other is not, place the admin first if a[i].Op && !a[j].Op { return true } else if !a[i].Op && a[j].Op { return false } // If both persons are admins or both are not, sort by name return a[i].Name < a[j].Name } func (a ByAdminAndName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func UserError(message string, username string, err error) { log.Error("message", message, log.Any("err", err), "username", username) } func userData(username, usersonline, ops string) UserInfo { regex := "(^| )" + username + "($| )" isonline, err := regexp.MatchString(string(regex), string(usersonline)) if err != nil { UserError("couldn't get online status for user", username, err) } isop, operr := regexp.MatchString(string(regex), string(ops)) if operr != nil { UserError("couldn't get op status for user", username, operr) } cmd := "/usr/bin/stat -c %W /home/" + username crd, crerr := exec.Command("bash", "-c", cmd).Output() if crerr != nil { UserError("couldn't stat user", username, crerr) } crdstr := strings.TrimSuffix(string(crd), "\n") filename := "/home/" + username + "/meta-info.toml" filestat, error := os.Stat(filename) if error != nil { if os.IsNotExist(error) { UserError("user doesn't have a meta-info.toml", username, error) var user UserInfo user.Name = username user.Created, _ = strconv.Atoi(crdstr) if isonline { user.Online = true } else { user.Online = false } if isop { user.Op = true } else { user.Op = false } return user } } // Check file size of meta-info.toml. If it's over 100 kB, bail if filestat.Size() > 100_000 { log.Error("user's meta-info.toml is above the 100 kB file size limit", "user", username) user := UserInfo{ Name: username, Online: false, Op: false, } user.Created, _ = strconv.Atoi(crdstr) if isonline { user.Online = true } if isop { user.Op = true } return user } viper.SetConfigFile(filename) if err := viper.ReadInConfig(); err != nil { log.Error("message", "couldn't read a users meta-info.toml file.", "error", log.Any("err", err), "user", username) user := UserInfo{ Name: username, } user.Created, _ = strconv.Atoi(crdstr) if isonline { user.Online = true } else { user.Online = false } if isop { user.Op = true } else { user.Op = false } return user } user := UserInfo{ Name: username, FullName: viper.GetString("fullname"), Capsule: viper.GetString("gemini"), Website: viper.GetString("website"), Desc: viper.GetString("description"), Email: viper.GetString("email"), Matrix: viper.GetString("matrix"), Fediverse: viper.GetString("fediverse"), Loc: viper.GetString("location"), Op: false, Online: false, } user.Created, _ = strconv.Atoi(crdstr) if isop { user.Op = true } if isonline { user.Online = true } return user } func UsersPage(c *fiber.Ctx) error { if runtime.GOOS == "windows" { return c.JSON(fiber.Map{ "message": "/users is not supported on Windows", "status": c.Response().StatusCode(), }) } // Get the number of users online usersOnline, err := exec.Command("bash", "-c", "/usr/bin/users").Output() if err != nil { log.Error("error", log.Any("error", err)) return c.SendStatus(fiber.StatusInternalServerError) } usersOnlineStr := string(usersOnline) usersOnlineDedup := utils.Dedup(usersOnlineStr) var output int if strings.Contains(usersOnlineDedup, " ") { outputa := int(strings.Count(usersOnlineDedup, " ")) output = outputa + 1 } else if usersOnlineDedup == "" { output = 0 } else { output = 1 } // Get OPs ops, opserr := exec.Command("bash", "-c", "/usr/bin/members sudo").Output() if opserr != nil { log.Error("couldn't get ops", "error", log.Any("err", opserr)) return c.SendStatus(fiber.StatusInternalServerError) } opstr := string(ops) // Get all users users, err2 := exec.Command("bash", "-c", "/usr/bin/ls /home").Output() if err2 != nil { log.Error("couldn't get all users", "error", log.Any("err", err2)) return c.SendStatus(fiber.StatusInternalServerError) } userstr := strings.TrimSuffix(string(users), "\n") usersarr := strings.Split(userstr, "\n") // Fill user info var userInfoArray []UserInfo for i := 0; i < len(usersarr); i++ { uname := string(usersarr[i]) userInfoArray = append( userInfoArray, userData( uname, strings.TrimSuffix(usersOnlineDedup, "\n"), strings.TrimSuffix(opstr, "\n"), ), ) } sort.Sort(ByAdminAndName(userInfoArray)) data := UserStruct{ Status: c.Response().StatusCode(), Online: output, Users: userInfoArray, Total: int(strings.Count(userstr, "\n")), } return c.JSON(data) }