managers.go (8067B)
1 package managers 2 3 import ( 4 "dkforest/pkg/hashset" 5 "encoding/json" 6 "fmt" 7 "sort" 8 "strings" 9 "sync" 10 "time" 11 12 "dkforest/pkg/database" 13 14 "dkforest/pkg/utils" 15 ) 16 17 func init() { 18 ActiveUsers = NewActiveUsersManager() 19 } 20 21 type UserInfo struct { 22 UserID database.UserID 23 Username database.Username 24 Color string 25 RefreshRate int64 26 LastUpdate time.Time 27 LastActivity *time.Time 28 IsModerator bool 29 IsIncognito bool 30 IsHellbanned bool 31 AfkIndicatorEnabled bool 32 } 33 34 type IUserInfoUser interface { 35 GetID() database.UserID 36 GetUsername() database.Username 37 GetRefreshRate() int64 38 GetChatColor() string 39 IsModerator() bool 40 GetIsIncognito() bool 41 GetIsHellbanned() bool 42 GetAFK() bool 43 GetAfkIndicatorEnabled() bool 44 } 45 46 func newUserInfo(user IUserInfoUser, lastActivity *time.Time) UserInfo { 47 return UserInfo{ 48 UserID: user.GetID(), 49 Username: user.GetUsername(), 50 RefreshRate: user.GetRefreshRate(), 51 Color: user.GetChatColor(), 52 IsModerator: user.IsModerator(), 53 IsIncognito: user.GetIsIncognito(), 54 IsHellbanned: user.GetIsHellbanned(), 55 AfkIndicatorEnabled: user.GetAFK() && user.GetAfkIndicatorEnabled(), 56 LastUpdate: time.Now(), 57 LastActivity: lastActivity, 58 } 59 } 60 61 func NewUserInfo(user IUserInfoUser) UserInfo { 62 return newUserInfo(user, nil) 63 } 64 65 func NewUserInfoUpdateActivity(user IUserInfoUser) UserInfo { 66 now := time.Now() 67 return newUserInfo(user, &now) 68 } 69 70 func (m UserInfo) MarshalJSON() ([]byte, error) { 71 return json.Marshal(&struct { 72 Username database.Username 73 Color string 74 }{ 75 Username: m.Username, 76 Color: m.Color, 77 }) 78 } 79 80 type UsersMap map[database.Username]UserInfo // Username -> UserInfo 81 82 func (m UsersMap) ToArray() []UserInfo { 83 out := make([]UserInfo, len(m)) 84 i := 0 85 for _, userInfo := range m { 86 out[i] = userInfo 87 i++ 88 } 89 sort.Slice(out, func(i, j int) bool { 90 if out[i].LastActivity != nil && out[j].LastActivity != nil { 91 if out[i].LastActivity.After(*out[j].LastActivity) { 92 return true 93 } else if out[i].LastActivity.Before(*out[j].LastActivity) { 94 return false 95 } 96 } 97 return out[i].Username < out[j].Username 98 }) 99 return out 100 } 101 102 const privateRoomKeyPrefix = "p_" 103 104 type RoomKey string 105 106 func (r RoomKey) isPrivateRoom() bool { 107 return strings.HasPrefix(string(r), privateRoomKeyPrefix) 108 } 109 110 func getRoomKey(room database.ChatRoom) RoomKey { 111 if room.IsProtected() { 112 return RoomKey(fmt.Sprintf("%s%d", privateRoomKeyPrefix, room.ID)) 113 } 114 return RoomKey(utils.FormatInt64(int64(room.ID))) 115 } 116 117 type ActiveUsersManager struct { 118 sync.RWMutex 119 activeUsers map[RoomKey]UsersMap 120 } 121 122 func NewActiveUsersManager() *ActiveUsersManager { 123 m := new(ActiveUsersManager) 124 m.activeUsers = make(map[RoomKey]UsersMap) 125 return m 126 } 127 128 var ActiveUsers *ActiveUsersManager 129 130 func (m *ActiveUsersManager) UpdateUserInRoom(room database.ChatRoom, userInfo UserInfo) { 131 if userInfo.IsIncognito { 132 return 133 } 134 roomKey := getRoomKey(room) 135 usersMap := m.getRoomUsersMap(roomKey) 136 137 m.RLock() 138 prevUserInfo := usersMap[userInfo.Username] 139 m.RUnlock() 140 if prevUserInfo.LastActivity == nil && userInfo.LastActivity == nil { 141 now := time.Now() 142 userInfo.LastActivity = &now 143 } else if userInfo.LastActivity == nil { 144 userInfo.LastActivity = prevUserInfo.LastActivity 145 } 146 147 m.Lock() 148 usersMap[userInfo.Username] = userInfo 149 m.activeUsers[roomKey] = usersMap 150 m.Unlock() 151 } 152 153 // UpdateUserHBInRooms Update the IsHellbanned property for a user. 154 // This is needed to ensure the user become invisible at the same time as his messages disappears. 155 // Otherwise, it is possible that the message becomes HB and the user still show up in the users list. 156 func (m *ActiveUsersManager) UpdateUserHBInRooms(newUserInfo UserInfo) { 157 m.Lock() 158 for roomKey, usersMap := range m.activeUsers { 159 for username, userInfo := range usersMap { 160 if userInfo.UserID == newUserInfo.UserID { 161 prevUserInfo := m.activeUsers[roomKey][username] 162 prevUserInfo.IsHellbanned = newUserInfo.IsHellbanned 163 m.activeUsers[roomKey][username] = prevUserInfo 164 } 165 } 166 } 167 m.Unlock() 168 } 169 170 func (m *ActiveUsersManager) getRoomUsersMap(roomKey RoomKey) UsersMap { 171 emptyUsersMap := make(UsersMap) 172 m.RLock() 173 usersMap, found := m.activeUsers[roomKey] 174 m.RUnlock() 175 if !found { 176 m.Lock() 177 m.activeUsers[roomKey] = emptyUsersMap 178 usersMap = emptyUsersMap 179 m.Unlock() 180 } 181 return usersMap 182 } 183 184 func (m *ActiveUsersManager) LocateUser(target database.Username) (out []database.RoomID) { 185 m.RLock() 186 for roomKey, usersMap := range m.activeUsers { 187 for username := range usersMap { 188 if strings.ToLower(string(username)) == strings.ToLower(string(target)) { 189 roomID := database.RoomID(utils.DoParseInt64(string(roomKey))) 190 out = append(out, roomID) 191 } 192 } 193 } 194 m.RUnlock() 195 return 196 } 197 198 // GetActiveUsers gets a list of all users that are in public rooms. 199 // We use this to display online users on the home (login) page if the feature is enabled. 200 func (m *ActiveUsersManager) GetActiveUsers() []UserInfo { 201 activeUsers := make(UsersMap) 202 m.RLock() 203 defer m.RUnlock() 204 for roomKey, usersMap := range m.activeUsers { 205 for username, userInfo := range usersMap { 206 if !roomKey.isPrivateRoom() { // Skip people who are in private rooms 207 activeUsers[username] = userInfo 208 } 209 } 210 } 211 return activeUsers.ToArray() 212 } 213 214 func GetUserIgnoreSet(db *database.DkfDB, authUser *database.User) *hashset.HashSet[database.Username] { 215 ignoredSet := hashset.New[database.Username]() 216 // Only fill the ignored set if the user does not display the ignored users ("Toggle ignored" chat setting) 217 // and if the user has "Hide ignored users from users lists" enabled (user setting) 218 if !authUser.DisplayIgnored && authUser.HideIgnoredUsersFromList { 219 ignoredUsersUsernames, _ := db.GetIgnoredUsersUsernames(authUser.ID) 220 for _, ignoredUserUsername := range ignoredUsersUsernames { 221 ignoredSet.Insert(ignoredUserUsername) 222 } 223 } 224 return ignoredSet 225 } 226 227 func (m *ActiveUsersManager) GetRoomUsers(room database.ChatRoom, ignoredSet *hashset.HashSet[database.Username]) (inRoom, inChat []UserInfo) { 228 outsideUsers := make(UsersMap) 229 newRoomUsersMap := make(UsersMap) 230 roomIDStr := getRoomKey(room) 231 // clone managers map into local variable map 232 m.RLock() 233 defer m.RUnlock() 234 if roomUsersMap, ok := m.activeUsers[roomIDStr]; ok { 235 for username, userInfo := range roomUsersMap { 236 newRoomUsersMap[username] = userInfo 237 } 238 } 239 // Build map of users outside of current room 240 for roomKey, usersMap := range m.activeUsers { 241 for username, userInfo := range usersMap { 242 if roomKey == roomIDStr || roomKey.isPrivateRoom() { // Skip people who are in private rooms 243 continue 244 } 245 // Only add users if they're not already in the current room 246 if _, ok := newRoomUsersMap[username]; !ok { 247 outsideUsers[username] = userInfo 248 } 249 } 250 } 251 // Delete ignored users 252 ignoredSet.Each(func(ignoreUsername database.Username) { 253 delete(newRoomUsersMap, ignoreUsername) 254 delete(outsideUsers, ignoreUsername) 255 }) 256 inRoom = newRoomUsersMap.ToArray() 257 inChat = outsideUsers.ToArray() 258 return 259 } 260 261 // RemoveUser from active users 262 func (m *ActiveUsersManager) RemoveUser(userID database.UserID) { 263 m.Lock() 264 defer m.Unlock() 265 for _, usersMap := range m.activeUsers { 266 for k, v := range usersMap { 267 if v.UserID == userID { 268 delete(usersMap, k) 269 } 270 } 271 } 272 } 273 274 func (m *ActiveUsersManager) IsUserActiveInRoom(userID database.UserID, room database.ChatRoom) (found bool) { 275 m.RLock() 276 defer m.RUnlock() 277 usersMap, found := m.activeUsers[getRoomKey(room)] 278 if !found { 279 return false 280 } 281 for _, v := range usersMap { 282 if v.UserID == userID { 283 return true 284 } 285 } 286 return false 287 } 288 289 func (m *ActiveUsersManager) CleanupUsersCache() { 290 for { 291 select { 292 case <-time.After(10 * time.Second): 293 } 294 m.Lock() 295 for _, usersMap := range m.activeUsers { 296 for k, userInfo := range usersMap { 297 if time.Since(userInfo.LastUpdate) > time.Duration(userInfo.RefreshRate+25)*time.Second { 298 delete(usersMap, k) 299 } 300 } 301 } 302 m.Unlock() 303 } 304 }