dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

utils.go (8188B)


      1 package utils
      2 
      3 import (
      4 	"dkforest/pkg/config"
      5 	"dkforest/pkg/database"
      6 	"dkforest/pkg/managers"
      7 	"dkforest/pkg/utils"
      8 	"errors"
      9 	"fmt"
     10 	"github.com/labstack/echo"
     11 	"github.com/sirupsen/logrus"
     12 )
     13 
     14 func GetZeroUser(db *database.DkfDB) database.User {
     15 	zeroUser, err := db.GetUserByUsername(config.NullUsername)
     16 	utils.LogErr(err)
     17 	return zeroUser
     18 }
     19 
     20 func ZeroSendMsg(db *database.DkfDB, recipientID database.UserID, msg string) {
     21 	zeroUser := GetZeroUser(db)
     22 	_, _ = db.CreateMsg(msg, msg, "", config.GeneralRoomID, zeroUser.ID, &recipientID, false)
     23 }
     24 
     25 func RootAdminNotify(db *database.DkfDB, msg string) {
     26 	rootAdminID := database.UserID(config.RootAdminID)
     27 	ZeroSendMsg(db, rootAdminID, msg)
     28 }
     29 
     30 func SendNewChessGameMessages(db *database.DkfDB, key, roomKey string, roomID database.RoomID, zeroUser, player1, player2 database.User) {
     31 	// Send game link to players
     32 	getPlayerMsg := func(opponent database.User) (raw string, msg string) {
     33 		raw = `Chess game against ` + string(opponent.Username)
     34 		msg = `<a href="/chess/` + key + `" rel="noopener noreferrer" target="_blank">Chess game against ` + string(opponent.Username) + `</a>`
     35 		return
     36 	}
     37 	raw, msg := getPlayerMsg(player2)
     38 	_, _ = db.CreateMsg(raw, msg, roomKey, roomID, zeroUser.ID, &player1.ID, false)
     39 	raw, msg = getPlayerMsg(player1)
     40 	_, _ = db.CreateMsg(raw, msg, roomKey, roomID, zeroUser.ID, &player2.ID, false)
     41 
     42 	// Send notifications to chess games subscribers
     43 	raw = `Chess game: ` + string(player1.Username) + ` VS ` + string(player2.Username)
     44 	msg = `<a href="/chess/` + key + `" rel="noopener noreferrer" target="_blank">Chess game: ` + string(player1.Username) + ` VS ` + string(player2.Username) + `</a>`
     45 
     46 	activeUsers := managers.ActiveUsers.GetActiveUsers()
     47 	activeUsersIDs := make([]database.UserID, len(activeUsers))
     48 	for idx, activeUser := range activeUsers {
     49 		activeUsersIDs[idx] = activeUser.UserID
     50 	}
     51 
     52 	users, _ := db.GetOnlineChessSubscribers(activeUsersIDs)
     53 	for _, user := range users {
     54 		if user.ID == player1.ID || user.ID == player2.ID {
     55 			continue
     56 		}
     57 		// Make a copy of user ID, otherwise next iteration will overwrite the pointer
     58 		// and change data that was sent previously in the pubsub later on
     59 		userID := user.ID
     60 		_, _ = db.CreateMsg(raw, msg, roomKey, roomID, zeroUser.ID, &userID, false)
     61 	}
     62 }
     63 
     64 func DoParseUsernamePtr(v string) *database.Username {
     65 	if v == "" {
     66 		return nil
     67 	}
     68 	username := database.Username(v)
     69 	return &username
     70 }
     71 
     72 func GetUserIDFromUsername(db *database.DkfDB, u string) *database.UserID {
     73 	username := DoParseUsernamePtr(u)
     74 	if username == nil {
     75 		return nil
     76 	}
     77 	userID, err := db.GetUserIDByUsername(*username)
     78 	if err != nil {
     79 		return nil
     80 	}
     81 	return &userID
     82 }
     83 
     84 func DoParsePmDisplayMode(v string) database.PmDisplayMode {
     85 	p, err := utils.ParseInt64(v)
     86 	if err != nil {
     87 		return database.PmNoFilter
     88 	}
     89 	switch p {
     90 	case 1:
     91 		return database.PmOnly
     92 	case 2:
     93 		return database.PmNone
     94 	default:
     95 		return database.PmNoFilter
     96 	}
     97 }
     98 
     99 func Parse[T ~int64](v string) (out T, err error) {
    100 	p, err := utils.ParseInt64(v)
    101 	if err != nil {
    102 		return out, err
    103 	}
    104 	return T(p), nil
    105 }
    106 
    107 func DoParse[T ~int64](v string) (out T) {
    108 	out, _ = Parse[T](v)
    109 	return
    110 }
    111 
    112 func ParseUserID(v string) (database.UserID, error) {
    113 	return Parse[database.UserID](v)
    114 }
    115 
    116 func DoParseUserID(v string) (out database.UserID) {
    117 	return DoParse[database.UserID](v)
    118 }
    119 
    120 func ParseRoomID(v string) (database.RoomID, error) {
    121 	return Parse[database.RoomID](v)
    122 }
    123 
    124 func DoParseRoomID(v string) (out database.RoomID) {
    125 	return DoParse[database.RoomID](v)
    126 }
    127 
    128 func SelfHellBan(db *database.DkfDB, user *database.User, msg string) {
    129 	db.NewAudit(*user, fmt.Sprintf("hellban %s #%d %s", user.Username, user.ID, msg))
    130 	user.HellBan(db)
    131 	managers.ActiveUsers.UpdateUserHBInRooms(managers.NewUserInfo(user))
    132 }
    133 
    134 func Kick(db *database.DkfDB, kicked, kickedBy database.User, purge, silent bool) error {
    135 	if kicked.IsHellbanned {
    136 		silent = true
    137 	}
    138 	return kick(db, kicked, kickedBy, silent, purge, "")
    139 }
    140 
    141 func SilentKick(db *database.DkfDB, kicked, kickedBy database.User) error {
    142 	return kick(db, kicked, kickedBy, true, true, "")
    143 }
    144 
    145 func SelfKick(db *database.DkfDB, kicked database.User, silent bool, msg string) error {
    146 	return kick(db, kicked, kicked, silent, true, msg)
    147 }
    148 
    149 func kick(db *database.DkfDB, kicked, kickedBy database.User, silent, purge bool, msg string) error {
    150 	if !kicked.Verified {
    151 		return errors.New("user already kicked")
    152 	}
    153 	// Can't kick a vetted user (unless admin)
    154 	if !kickedBy.IsAdmin && kicked.Vetted {
    155 		return errors.New("cannot kick a vetted user")
    156 	}
    157 	// Can't kick another moderator (unless admin)
    158 	if !kickedBy.IsAdmin && kicked.IsModerator() {
    159 		return errors.New("cannot kick another moderator")
    160 	}
    161 	// Can't kick yourself as a moderator/admin
    162 	if (kicked.IsAdmin || kicked.IsModerator()) && kickedBy.ID == kicked.ID {
    163 		return errors.New("cannot kick yourself")
    164 	}
    165 
    166 	db.NewAudit(kickedBy, fmt.Sprintf("kick %s #%d %s", kicked.Username, kicked.ID, msg))
    167 	kicked.SetVerified(db, false)
    168 
    169 	// Remove user from the user cache
    170 	managers.ActiveUsers.RemoveUser(kicked.ID)
    171 
    172 	if purge {
    173 		// Purge user messages
    174 		if err := db.DeleteUserChatMessages(kicked.ID); err != nil {
    175 			logrus.Error(err)
    176 		}
    177 		database.MsgPubSub.Pub(database.RefreshTopic, database.ChatMessageType{Typ: database.ForceRefresh})
    178 	} else {
    179 		database.MsgPubSub.Pub("refresh_"+string(kicked.Username), database.ChatMessageType{Typ: database.ForceRefresh})
    180 	}
    181 
    182 	// If user is HB, do not display system message
    183 	if !silent {
    184 		// Display kick message
    185 		db.CreateKickMsg(kicked, kickedBy)
    186 	}
    187 
    188 	return nil
    189 }
    190 
    191 func GetRoomAndKey(db *database.DkfDB, c echo.Context, roomName string) (database.ChatRoom, string, error) {
    192 	roomKey := ""
    193 	room, err := db.GetChatRoomByName(roomName)
    194 	if err != nil {
    195 		return room, roomKey, errors.New("room not found")
    196 	}
    197 	hasAccess, roomKey := room.HasAccess(c)
    198 	if !hasAccess {
    199 		return room, roomKey, errors.New("forbidden")
    200 	}
    201 	return room, roomKey, nil
    202 }
    203 
    204 var ErrPMDenied = errors.New("you cannot pm/inbox this user")
    205 var Err20Msgs = errors.New("you need 20 public messages to unlock PMs/Inbox; or be whitelisted")
    206 var ErrOther20Msgs = errors.New("dest user must be whitelisted or have 20 public messages")
    207 
    208 func CanUserPmOther(db *database.DkfDB, user, other database.User, roomIsPrivate bool) (skipInbox bool, err error) {
    209 	errPMDenied := ErrPMDenied
    210 
    211 	if user.ID == other.ID {
    212 		return false, errors.New("cannot /pm yourself")
    213 	}
    214 
    215 	if db.IsUserPmWhitelisted(user.ID, other.ID) {
    216 		return false, nil
    217 	}
    218 
    219 	switch other.PmMode {
    220 	case database.PmModeWhitelist:
    221 		// We are in whitelist mode, and user is not whitelisted
    222 		return false, errPMDenied
    223 
    224 	case database.PmModeStandard:
    225 		if !user.CanSendPM() {
    226 			// In private rooms, can send PM but inboxes will be skipped if not enough public messages
    227 			if roomIsPrivate {
    228 				return true, nil
    229 			}
    230 			// Need at least 20 public messages to send PM in a public room
    231 			return false, Err20Msgs
    232 		}
    233 
    234 		// User on blacklist cannot PM/Inbox
    235 		if db.IsUserPmBlacklisted(user.ID, other.ID) {
    236 			return false, errPMDenied
    237 		}
    238 		// Other doesn't want PM from new users
    239 		if !user.AccountOldEnough() && other.BlockNewUsersPm {
    240 			return false, errPMDenied
    241 		}
    242 
    243 		if !other.CanSendPM() {
    244 			if db.IsUserPmWhitelisted(other.ID, user.ID) {
    245 				return true, nil
    246 			}
    247 			// In private rooms, can send PM but inboxes will be skipped if not enough public messages
    248 			if roomIsPrivate {
    249 				return true, nil
    250 			}
    251 			return false, ErrOther20Msgs
    252 		}
    253 
    254 		return false, nil
    255 	}
    256 
    257 	// Should never go here
    258 	return false, nil
    259 }
    260 
    261 // VerifyMsgAuth returns either or not authUser is allowed to see msg
    262 func VerifyMsgAuth(db *database.DkfDB, msg *database.ChatMessage, authUserID database.UserID, isModerator bool) bool {
    263 	// Verify moderators channel authorization
    264 	if msg.Moderators && !isModerator {
    265 		return false
    266 	}
    267 	// Verify group authorization
    268 	if msg.GroupID != nil {
    269 		userGroupsIDs, _ := db.GetUserRoomGroupsIDs(authUserID, msg.RoomID)
    270 		if !utils.InArr(*msg.GroupID, userGroupsIDs) {
    271 			return false
    272 		}
    273 	}
    274 	// verify PM authorization
    275 	if msg.IsPm() {
    276 		if msg.UserID != authUserID && *msg.ToUserID != authUserID {
    277 			return false
    278 		}
    279 	}
    280 	return true
    281 }