// myUnreviewed provides functions to get message, profile, and attribute hashes the user has not yet reviewed package myUnreviewed // Each content to review has a priority // Content that has been reviewed by the fewest moderators will have the highest priority // Content that has been sufficiently reviewed by other moderators will have a lower priority // Unreviewed content will be retained by the client until it expires from the network //TODO: Add filter for ProfileLanguage, so user will only review content they understand //TODO: Add higher priority for reported profiles? //TODO: Add limit where if it has found enough options to review, break // -This means we will only need to search through a maximum number of contents before we settle on one that is the highest priority // -It may not be the highest priority out of all contents, but it will be the highest priority out of a subset of contents // -This would make the retrieval faster //TODO: Extract errantProfiles from identity reviews and treat them like profile reports //TODO: Add a toggle to consider/not consider profiles/messages reviewed if we have banned the author //TODO: Build a cache to store the highest priority unreviewed content hashes // -This way we only have to repeat the process after the highest priority unreviewed content has been reviewed import "seekia/internal/moderation/myReviews" import "seekia/internal/moderation/verifiedVerdict" import "seekia/internal/moderation/mySkippedContent" import "seekia/internal/moderation/myHiddenContent" import "seekia/internal/profiles/profileStorage" import "seekia/internal/profiles/readProfiles" import "seekia/internal/messaging/chatMessageStorage" import "seekia/internal/myRanges" import "seekia/internal/badgerDatabase" import "seekia/internal/helpers" import "strings" import "errors" import "slices" //Outputs: // -bool: Any unreviewed message hash exists // -[26]byte: Message Hash // -error func GetMyHighestPriorityUnreviewedMessageHash(networkType byte, imageOrText string)(bool, [26]byte, error){ isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, [26]byte{}, errors.New("GetMyHighestPriorityUnreviewedMessageHash called with invalid networkType: " + networkTypeString) } if (imageOrText != "Image" && imageOrText != "Text"){ return false, [26]byte{}, errors.New("GetMyHighestPriorityUnreviewedMessageHash called with invalid imageOrText: " + imageOrText) } allReviewedMessageHashesList, err := badgerDatabase.GetAllReviewedMessageHashes() if (err != nil) { return false, [26]byte{}, err } allReportedMessageHashesList, err := badgerDatabase.GetAllReportedMessageHashes() if (err != nil) { return false, [26]byte{}, err } allMessageHashesList := helpers.CombineTwoListsAndAvoidDuplicates(allReviewedMessageHashesList, allReportedMessageHashesList) myModeratorIdentityExists, myReviewedMessageHashesList, err := myReviews.GetMyReviewedMessageHashesList(networkType) if (err != nil) { return false, [26]byte{}, err } if (myModeratorIdentityExists == false) { return false, [26]byte{}, errors.New("GetMyHighestPriorityUnreviewedMessageHash called when my moderator identity does not exist.") } myIdentityExists, myBannedIdentityHashesList, err := myReviews.GetMyBannedIdentityHashesList(networkType) if (err != nil) { return false, [26]byte{}, err } if (myIdentityExists == false) { return false, [26]byte{}, errors.New("My moderator identity not found after being found already.") } leastReviewedMessageFound := false var leastReviewedMessageHash [26]byte leastReviewedMessageNumberOfReviews := 0 // We will only serve skipped messages if all messages remaining to review are skipped // This bool will be true, unless we encounter a non-skipped message // If we do, we will overwrite the existing least reviewed messageHash and reject all skipped messages going forward allMessagesAreSkipped := true for _, messageHash := range allMessageHashesList{ iHaveReviewed := slices.Contains(myReviewedMessageHashesList, messageHash) if (iHaveReviewed == true){ continue } messageMetadataIsKnown, _, messageNetworkType, messageInbox, _, downloadingRequiredReviews, parametersExist, _, numberApprove, numberBan, _, _, _, _, err := verifiedVerdict.GetVerifiedMessageVerdict(messageHash) if (err != nil) { return false, [26]byte{}, err } if (messageMetadataIsKnown == false){ // We do not have the message downloaded. It is not eligible for review continue } if (messageNetworkType != networkType){ // Message belongs to a different networkType. continue } if (downloadingRequiredReviews == false){ // Message is not within our moderation range. Skip message continue } if (parametersExist == false){ // Moderation parameters need to be downloaded to moderate content. Stop searching for messages. break } moderatorModeEnabled, isInMyModerationRange, err := myRanges.CheckIfMessageInboxIsWithinMyModerationRange(messageInbox) if (err != nil) { return false, [26]byte{}, err } if (moderatorModeEnabled == false){ // Mode must have been disabled during generation. // No messages to review. return false, [26]byte{}, nil } if (isInMyModerationRange == false){ // Message is outside of our moderation range. Skip it. continue } messageExists, cipherKeyFound, messageIsDecryptable, _, senderIdentityHash, messageCommunication, err := chatMessageStorage.GetDecryptedMessageForModeration(messageHash) if (err != nil) { return false, [26]byte{}, err } if (messageExists == false){ // We cannot review messages which we do not have downloaded continue } if (cipherKeyFound == false){ // We do not have a cipher key // We cannot view the message contents continue } if (messageIsDecryptable == false){ // We cannot decrypt the message // Message author must be malicious // We should ban these kinds of messages automatically, and ban anyone who approves them continue } identityIsBanned := slices.Contains(myBannedIdentityHashesList, senderIdentityHash) if (identityIsBanned == true){ // We already banned the author of the message // Review is not necessary continue } getImageOrTextStatus := func()string{ isImage := strings.HasPrefix(messageCommunication, ">!>Photo=") if (isImage == true){ return "Image" } return "Text" } imageOrTextStatus := getImageOrTextStatus() if (imageOrTextStatus != imageOrText){ // Message is not the imageOrText that we are interested in. continue } messageIsHidden, _, _, err := myHiddenContent.CheckIfMessageIsHidden(messageHash) if (err != nil) { return false, [26]byte{}, err } if (messageIsHidden == true){ // The moderator has hidden this message. continue } messageIsSkipped, _, _, err := mySkippedContent.CheckIfMessageIsSkipped(messageHash) if (err != nil) { return false, [26]byte{}, err } if (messageIsSkipped == false){ if (allMessagesAreSkipped == true){ // This is the first non-skipped message we have encountered // This may not be the first message we have encountered // Thus, we must clear any previously found content leastReviewedMessageFound = false leastReviewedMessageHash = [26]byte{} leastReviewedMessageNumberOfReviews = 0 allMessagesAreSkipped = false } } else { if (allMessagesAreSkipped == false){ // We have some non-skipped content to review, so we will reject all skipped content continue } } numberOfReviewers := numberApprove + numberBan if (numberOfReviewers == 0 && messageIsSkipped == false){ // Message is high priority, it has no reviews. We stop searching. return true, messageHash, nil } if (leastReviewedMessageFound == false || numberOfReviewers < leastReviewedMessageNumberOfReviews){ leastReviewedMessageFound = true leastReviewedMessageHash = messageHash leastReviewedMessageNumberOfReviews = numberOfReviewers } } if (leastReviewedMessageFound == false){ // Nothing left to review. return false, [26]byte{}, nil } return true, leastReviewedMessageHash, nil } // This function only returns full profiles that have been banned or reported, without reference to the specific attribute // To approve these profiles, moderators must approve the full profile, which requires viewing all attributes of the profile, including canonical ones // For profiles whose reviewed attributes have been specified, use GetMyHighestPriorityUnreviewedProfileAttributeHash instead //Outputs: // -bool: Unreviewed profile exists // -[28]byte: Highest priority unreviewed profile hash // -error func GetMyHighestPriorityUnreviewedProfileHash(profileType string, networkType byte)(bool, [28]byte, error){ if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){ return false, [28]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileHash called with invalid profileType: " + profileType) } isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, [28]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileHash called with invalid networkType: " + networkTypeString) } // For each profile, we must determine if we have reviewed it // If we have approved/banned the entire profile, then it is considered reviewed // If we have banned any attribute of the profile, then it is considered reviewed moderatorIdentityExists, myReviewedProfileHashesMap, err := myReviews.GetMyReviewedProfileHashesMap(profileType, networkType) if (err != nil) { return false, [28]byte{}, err } if (moderatorIdentityExists == false) { return false, [28]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileHash called when my moderator identity does not exist.") } myIdentityExists, myBannedIdentityHashesList, err := myReviews.GetMyBannedIdentityHashesList(networkType) if (err != nil) { return false, [28]byte{}, err } if (myIdentityExists == false) { return false, [28]byte{}, errors.New("My moderator identity not found after being found already.") } allReviewedProfileHashesList, err := badgerDatabase.GetAllReviewedProfileHashes() if (err != nil) { return false, [28]byte{}, err } allReportedProfileHashesList, err := badgerDatabase.GetAllReportedProfileHashes() if (err != nil) { return false, [28]byte{}, err } allProfileHashesList := helpers.CombineTwoListsAndAvoidDuplicates(allReviewedProfileHashesList, allReportedProfileHashesList) leastReviewedProfileHashFound := false var leastReviewedProfileHash [28]byte leastReviewedProfileHashReviewersCount := 0 // We will only serve skipped profiles if all remaining profiles to review are skipped // This bool will be true, unless we encounter a non-skipped profile // If we do, we will overwrite the existing least reviewed profileHash and reject all skipped profiles going forward allProfilesAreSkipped := true for _, profileHash := range allProfileHashesList{ _, iHaveReviewed := myReviewedProfileHashesMap[profileHash] if (iHaveReviewed == true){ // We have already reviewed the profile, or banned one of its attributes continue } profileIsHidden, _, _, err := myHiddenContent.CheckIfProfileIsHidden(profileHash) if (err != nil) { return false, [28]byte{}, err } if (profileIsHidden == true){ // The moderator has hidden this profile. continue } exists, _, err := profileStorage.GetStoredProfile(profileHash) if (err != nil) { return false, [28]byte{}, err } if (exists == false){ // We need the profile bytes to review the profile continue } profileIsDisabled, profileMetadataIsKnown, _, profileNetworkType, profileAuthor, _, downloadingRequiredReviews, parametersExist, _, approveAdvocatesCount, banAdvocatesCount, _, _, _, _, _, _, _, _, fullProfileBanAdvocatesCount, _, _, err := verifiedVerdict.GetVerifiedProfileVerdict(profileHash) if (err != nil) { return false, [28]byte{}, err } if (profileIsDisabled == true){ // No review is needed. Skip continue } if (profileMetadataIsKnown == false){ // We do not have the profile downloaded, we cannot review it. // It must have been deleted after GetAllProfileHashes step. continue } if (profileNetworkType != networkType){ // Profile belongs to a different networkType. continue } if (downloadingRequiredReviews == false){ // Message is not within our moderation range. Skip profile continue } if (parametersExist == false){ // Moderation parameters need to be downloaded to moderate content. Stop searching for profiles. return false, [28]byte{}, nil } profileIsReported := slices.Contains(allReportedProfileHashesList, profileHash) if (fullProfileBanAdvocatesCount == 0 && profileIsReported == false){ // Nobody has banned/reported the full profile. // This means that the profile can be reviewed by reviewing attributes only // We skip this profile continue } moderatorModeEnabled, isWithinMyRange, err := myRanges.CheckIfIdentityHashIsWithinMyModerationRange(profileAuthor) if (err != nil) { return false, [28]byte{}, err } if (moderatorModeEnabled == false){ // Mode must have been disabled after previous check. Return no profiles to review. return false, [28]byte{}, nil } if (isWithinMyRange == false){ // Profile is outside of our moderation range, skip it. continue } iHaveBannedIdentity := slices.Contains(myBannedIdentityHashesList, profileAuthor) if (iHaveBannedIdentity == true){ // We have banned the profile author // Reviewing the profile is not necessary continue } profileIsSkipped, _, _, err := mySkippedContent.CheckIfProfileIsSkipped(profileHash) if (err != nil) { return false, [28]byte{}, err } if (profileIsSkipped == false){ if (allProfilesAreSkipped == true){ // This is the first non-skipped profile we have encountered // This may not be the first profile we have encountered // Thus, we must clear any previously found profiles leastReviewedProfileHashFound = false leastReviewedProfileHash = [28]byte{} leastReviewedProfileHashReviewersCount = 0 allProfilesAreSkipped = false } } else { if (allProfilesAreSkipped == false){ // We have some non-skipped content to review, so we will reject all skipped content continue } } numberOfEligibleReviewers := approveAdvocatesCount + banAdvocatesCount if (approveAdvocatesCount == 0 && profileIsSkipped == false){ // This profile has been either been reported or banned, and has no approvals by anyone eligible // We consider this the highest priority kind of profile. // Stop searching and return this profile return true, profileHash, nil } if (leastReviewedProfileHashFound == false || leastReviewedProfileHashReviewersCount < numberOfEligibleReviewers){ leastReviewedProfileHashFound = true leastReviewedProfileHash = profileHash leastReviewedProfileHashReviewersCount = numberOfEligibleReviewers } } if (leastReviewedProfileHashFound == false){ // No profiles to review return false, [28]byte{}, nil } return true, leastReviewedProfileHash, nil } //Outputs: // -bool: Any unreviewed profile attribute hash exists // -[27]byte: Profile attribute hash // -[28]byte: Profile hash that contained this attribute (there may be many, we will return the first one we encounter) // -[16]byte: Profile identity hash that authored the profile // -error func GetMyHighestPriorityUnreviewedProfileAttributeHash(profileType string, attributeIdentifier int, networkType byte)(bool, [27]byte, [28]byte, [16]byte, error){ if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){ return false, [27]byte{}, [28]byte{}, [16]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileAttributeHash called with invalid profileType: " + profileType) } isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, [27]byte{}, [28]byte{}, [16]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileAttributeHash called with invalid networkType: " + networkTypeString) } myIdentityExists, myReviewedProfileHashesMap, err := myReviews.GetMyReviewedProfileHashesMap(profileType, networkType) if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err } if (myIdentityExists == false) { return false, [27]byte{}, [28]byte{}, [16]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileAttributeHash called when my moderator identity does not exist.") } myIdentityExists, myReviewedAttributeHashesMap, err := myReviews.GetMyReviewedProfileAttributeHashesMap(networkType) if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err } if (myIdentityExists == false) { return false, [27]byte{}, [28]byte{}, [16]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileAttributeHash called when my moderator identity does not exist.") } myIdentityExists, myBannedIdentityHashesList, err := myReviews.GetMyBannedIdentityHashesList(networkType) if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err } if (myIdentityExists == false) { return false, [27]byte{}, [28]byte{}, [16]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileAttributeHash called when my moderator identity does not exist.") } allReportedAttributeHashesList, err := badgerDatabase.GetAllReportedProfileAttributeHashes() if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err } allProfileHashesList, err := badgerDatabase.GetAllProfileHashes(profileType) if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err } // This map will store the attribute hashes we have already checked // We need this because the same attribute may exist in multiple profiles encounteredAttributeHashesMap := make(map[[27]byte]struct{}) // We use the below variables to find the least reviewed (highest priority) attribute hash leastReviewedAttributeHashFound := false var leastReviewedAttributeHash [27]byte var leastReviewedAttributeProfileHash [28]byte var leastReviewedAttributeIdentityHash [16]byte leastReviewedAttributeHashReviewsCount := 0 // We will only serve skipped attributes if all remaining attributes to review are skipped // This bool will be true, unless we encounter a non-skipped attribute // If we do, we will overwrite the existing least reviewed attributeHash and reject all skipped attributes going forward allAttributesAreSkipped := true for _, profileHash := range allProfileHashesList{ _, iHaveReviewed := myReviewedProfileHashesMap[profileHash] if (iHaveReviewed == true){ // We have already reviewed this profile. // We don't need to review any of its component attributes continue } exists, _, err := profileStorage.GetStoredProfile(profileHash) if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err } if (exists == false){ // We need the profile bytes to review the profile // Profile must have been deleted after we called GetAllProfileHashes continue } profileIsDisabled, profileMetadataIsKnown, _, profileNetworkType, profileAuthor, profileAttributeHashesMap, downloadingRequiredReviews, parametersExist, _, _, _, _, _, attributeApproveAdvocateCountsMap, attributeBanAdvocateCountsMap, _, _, _, _, _, _, allAttributeBanAdvocatesMap, err := verifiedVerdict.GetVerifiedProfileVerdict(profileHash) if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err } if (profileIsDisabled == true){ // No review is needed. Skip continue } if (profileMetadataIsKnown == false){ // We do not have the profile downloaded, we cannot review it. // It must have been deleted after GetAllProfileHashes step. continue } if (profileNetworkType != networkType){ // Profile belongs to a different networkType. continue } if (downloadingRequiredReviews == false){ // Profile is not within our moderation range. Skip message continue } if (parametersExist == false){ // Moderation parameters need to be downloaded to moderate content. Stop searching for profile attributes. break } moderatorModeEnabled, isWithinMyRange, err := myRanges.CheckIfIdentityHashIsWithinMyModerationRange(profileAuthor) if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err } if (moderatorModeEnabled == false){ // Mode must have been disabled after previous check. Return no profiles to review. return false, [27]byte{}, [28]byte{}, [16]byte{}, nil } if (isWithinMyRange == false){ // Profile is outside of our moderation range, skip it. continue } iHaveBannedIdentity := slices.Contains(myBannedIdentityHashesList, profileAuthor) if (iHaveBannedIdentity == true){ // We have already banned the profile author continue } attributeHash, exists := profileAttributeHashesMap[attributeIdentifier] if (exists == false){ // This profile does not have the attribute which we want to review continue } _, hasBeenEncountered := encounteredAttributeHashesMap[attributeHash] if (hasBeenEncountered == true){ // We already dealt with this attribute continue } encounteredAttributeHashesMap[attributeHash] = struct{}{} _, iHaveReviewed = myReviewedAttributeHashesMap[attributeHash] if (iHaveReviewed == true){ // We have already reviewed the attribute. continue } attributeIsHidden, _, _, err := myHiddenContent.CheckIfAttributeIsHidden(attributeHash) if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err } if (attributeIsHidden == true){ // The moderator has hidden this attribute. continue } // This will tell us if any moderators, banned or not, have banned the attribute checkIfAnyAttributeBanAdvocatesExist := func()bool{ allAttributeBanAdvocatesList, exists := allAttributeBanAdvocatesMap[attributeIdentifier] if (exists == false){ return false } if (len(allAttributeBanAdvocatesList) == 0){ return false } return true } anyAttributeBanAdvocatesExist := checkIfAnyAttributeBanAdvocatesExist() if (anyAttributeBanAdvocatesExist == false){ attributeIsReported := slices.Contains(allReportedAttributeHashesList, attributeHash) if (attributeIsReported == false){ // Nobody has banned/reported this attribute if (profileType != "Mate"){ // We do not need to review attributes that are not banned for non-mate profiles continue } attributeProfileType, attributeIsCanonical, err := readProfiles.ReadAttributeHashMetadata(attributeHash) if (err != nil){ return false, [27]byte{}, [28]byte{}, [16]byte{}, err } if (attributeProfileType != profileType){ return false, [27]byte{}, [28]byte{}, [16]byte{}, errors.New("GetVerifiedProfileVerdict returning attribute hash with different profileType than author.") } if (attributeIsCanonical == true){ // We don't need to review it, even if profileType == Mate continue } // We see if the user has a newer profile // If they do, we don't need to review this profile anyProfileExists, _, newestProfileHash, _, _, _, err := profileStorage.GetNewestUserProfile(profileAuthor, networkType) if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err } if (anyProfileExists == false){ // The user's profile must have been deleted after we performed GetAllProfileHashes continue } if (profileHash != newestProfileHash){ // There is a newer profile by this same Mate user // We don't need to review the current profile, because it is not banned by anyone, and none of its attributes are banned // We will review the newer profile instead continue } // We still need to review this attribute, because it is a non-canonical mate attribute } } // The attribute is worthy of review // We see if it is less reviewed than our current leastReviewedAttributeHash // This will tell us the number of eligible attribute approve advocates getAttributeApproveAdvocatesCount := func()int{ attributeApproveAdvocatesCount, exists := attributeApproveAdvocateCountsMap[attributeIdentifier] if (exists == false){ return 0 } return attributeApproveAdvocatesCount } // This will tell us the number of eligible attribute ban advocates getAttributeBanAdvocatesCount := func()int{ attributeBanAdvocatesCount, exists := attributeBanAdvocateCountsMap[attributeIdentifier] if (exists == false){ return 0 } return attributeBanAdvocatesCount } attributeApproveAdvocatesCount := getAttributeApproveAdvocatesCount() attributeBanAdvocatesCount := getAttributeBanAdvocatesCount() numberOfReviewers := attributeApproveAdvocatesCount + attributeBanAdvocatesCount attributeIsSkipped, _, _, err := mySkippedContent.CheckIfAttributeIsSkipped(attributeHash) if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err } if (attributeIsSkipped == false){ if (allAttributesAreSkipped == true){ // This is the first non-skipped attribute we have encountered // It may not be the first attribute we have encountered // Thus, we must clear any previously found attributes leastReviewedAttributeHashFound = false leastReviewedAttributeHash = [27]byte{} leastReviewedAttributeProfileHash = [28]byte{} leastReviewedAttributeIdentityHash = [16]byte{} leastReviewedAttributeHashReviewsCount = 0 allAttributesAreSkipped = false } } else { if (allAttributesAreSkipped == false){ // We have some non-skipped attributes, so we will reject all skipped attributes continue } } if (attributeApproveAdvocatesCount == 0 && attributeIsSkipped == false){ // This is the highest priority kind of profile attribute // It has no approvers, and needs to be reviewed // We stop searching and return the attribute return true, attributeHash, profileHash, profileAuthor, nil } if (leastReviewedAttributeHashFound == false || numberOfReviewers < leastReviewedAttributeHashReviewsCount){ leastReviewedAttributeHashFound = true leastReviewedAttributeHash = attributeHash leastReviewedAttributeProfileHash = profileHash leastReviewedAttributeIdentityHash = profileAuthor leastReviewedAttributeHashReviewsCount = numberOfReviewers } } if (leastReviewedAttributeHashFound == true){ return true, leastReviewedAttributeHash, leastReviewedAttributeProfileHash, leastReviewedAttributeIdentityHash, nil } // Nothing left to review return false, [27]byte{}, [28]byte{}, [16]byte{}, nil }