Search terms and admin features

This commit is contained in:
Noah Petherbridge 2024-07-13 12:05:36 -07:00
parent 2f997dfee0
commit a0320714c4
7 changed files with 181 additions and 1 deletions

View File

@ -1,6 +1,7 @@
package account
import (
"fmt"
"net/http"
"strconv"
@ -9,6 +10,7 @@ import (
"code.nonshy.com/nonshy/website/pkg/log"
"code.nonshy.com/nonshy/website/pkg/models"
"code.nonshy.com/nonshy/website/pkg/session"
"code.nonshy.com/nonshy/website/pkg/spam"
"code.nonshy.com/nonshy/website/pkg/templates"
"code.nonshy.com/nonshy/website/pkg/worker"
)
@ -50,7 +52,8 @@ func Search() http.HandlerFunc {
ageMin, ageMax = ageMax, ageMin
}
search := models.ParseSearchString(searchTerm)
rawSearch := models.ParseSearchString(searchTerm)
search, restricted := spam.RestrictSearchTerms(rawSearch)
// Get current user.
currentUser, err := session.CurrentUser(r)
@ -60,6 +63,32 @@ func Search() http.HandlerFunc {
return
}
// Report when search terms are restricted.
if restricted != nil {
// Admin users: allow the search anyway.
if currentUser.IsAdmin {
search = rawSearch
} else {
fb := &models.Feedback{
Intent: "report",
Subject: "Search Keyword Blacklist",
UserID: currentUser.ID,
TableName: "users",
TableID: currentUser.ID,
Message: fmt.Sprintf(
"A user has run a search on the Member Directory using search terms which are prohibited.\n\n"+
"Their search query was: %s",
searchTerm,
),
}
// Save the feedback.
if err := models.CreateFeedback(fb); err != nil {
log.Error("Couldn't save feedback from user updating their DOB: %s", err)
}
}
}
// Geolocation/Who's Nearby: if the current user uses GeoIP, update
// their coordinates now.
myLocation, err := models.RefreshGeoIP(currentUser.ID, r)
@ -160,6 +189,9 @@ func Search() http.HandlerFunc {
"LikedSearch": likedSearch,
"Sort": sort,
// Restricted Search errors.
"RestrictedSearchError": restricted,
// Photo counts mapped to users
"PhotoCountMap": models.MapPhotoCounts(users),

View File

@ -122,6 +122,34 @@ func UserActions() http.HandlerFunc {
session.FlashError(w, r, "Error getting blocklist insights: %s", err)
}
vars["BlocklistInsights"] = insights
case "essays":
// Edit their profile essays easily.
if !currentUser.HasAdminScope(config.ScopePhotoModerator) {
session.FlashError(w, r, "Missing admin scope: %s", config.ScopePhotoModerator)
templates.Redirect(w, "/admin")
return
}
if r.Method == http.MethodPost {
var (
about = r.PostFormValue("about_me")
interests = r.PostFormValue("interests")
musicMovies = r.PostFormValue("music_movies")
)
user.SetProfileField("about_me", about)
user.SetProfileField("interests", interests)
user.SetProfileField("music_movies", musicMovies)
if err := user.Save(); err != nil {
session.FlashError(w, r, "Error saving the user: %s", err)
} else {
session.Flash(w, r, "Their profile text has been updated!")
}
templates.Redirect(w, "/u/"+user.Username)
return
}
case "impersonate":
// Scope check.
if !currentUser.HasAdminScope(config.ScopeUserImpersonate) {

View File

@ -3,6 +3,8 @@ package spam
import (
"errors"
"strings"
"code.nonshy.com/nonshy/website/pkg/models"
)
// SpamWebsites to third-party video hosting apps: we already have our own chat room, and third-party links shared in
@ -30,3 +32,57 @@ func DetectSpamMessage(message string) error {
return nil
}
// RestrictSearchTerm can remove/replace search words to exclude blacklisted terms.
func RestrictSearchTerms(terms *models.Search) (*models.Search, error) {
var (
m = map[string]interface{}{}
result = &models.Search{}
r1, r2 int
)
// Map the blacklist for easy lookup.
for _, term := range restrictedSearchTerms {
m[term] = nil
}
// Filter the includes+excludes.
result.Includes, r1 = restrictTermsFrom(terms.Includes, m)
result.Excludes, r2 = restrictTermsFrom(terms.Excludes, m)
// If we have excluded everything down to zero.
if len(terms.Includes) > 0 && len(result.Includes) == 0 {
result.Includes = append(result.Includes, "36c49b88-dd0c-4b9f-a4e1-f0c73c976ce2")
}
// Were there restrictions?
if r1+r2 > 0 {
return result, errors.New("some search terms were restricted")
}
return result, nil
}
// Restricted search terms.
var restrictedSearchTerms = []string{
"open", "mind", "minded", "taboo", "tabboo", "perv", "pervy", "grew", "raised", "raise",
"children", "kid", "kids", "dad", "mom", "underage", "yng", "ynger", "family", "families",
"18", "shota", "shotacon", "loli", "lolicon", "jailbait", "incest",
}
func restrictTermsFrom(terms []string, blacklist map[string]interface{}) ([]string, int) {
var (
result []string
count int
)
for _, term := range terms {
if _, ok := blacklist[strings.ToLower(term)]; ok {
count++
continue
}
result = append(result, term)
}
return result, count
}

View File

@ -497,6 +497,12 @@
</li>
<p class="menu-label">Admin Actions</p>
<li>
<a href="/admin/user-action?intent=essays&user_id={{.User.ID}}">
<span class="icon"><i class="fa fa-pencil"></i></span>
<span>Edit their profile text</span>
</a>
</li>
<li>
<a href="/admin/user-action?intent=impersonate&user_id={{.User.ID}}">
<span class="icon"><i class="fa fa-ghost"></i></span>

View File

@ -69,6 +69,18 @@
</div>
{{end}}
<!-- Restricted search terms -->
{{if .RestrictedSearchError}}
<div class="notification is-danger is-light content">
<p>
<i class="fa fa-exclamation-triangle mr-1"></i>
<strong>Notice:</strong> Some of your search terms will be ignored, as we think
they may be associated to child sexual abuse.
Please review the <a href="/tos#child-exploitation">Terms of Service</a> for more information.
</p>
</div>
{{end}}
<div class="block">
<div class="card nonshy-collapsible-mobile">

View File

@ -264,6 +264,14 @@
{{end}}
</td>
</tr>
<tr>
<td class="has-text-right">
<strong>Date:</strong>
</td>
<td>
{{.CreatedAt.Format "2006-01-02 15:04:05 MST"}}
</td>
</tr>
</table>
<div class="content">

View File

@ -22,6 +22,9 @@
{{if eq .Intent "impersonate"}}
<i class="mr-2 fa fa-ghost"></i>
Impersonate User
{{else if eq .Intent "essays"}}
<i class="mr-2 fa fa-pencil"></i>
Edit Profile Text
{{else if eq .Intent "ban"}}
<i class="mr-2 fa fa-ban"></i>
Ban User
@ -145,6 +148,41 @@
</div>
</div>
</div>
{{else if eq .Intent "essays"}}
<div class="block content">
<p>
You may use this page to edit the essay texts (e.g. About Me) section of a user's profile page.
The main use cases may be to remove Onlyfans spammy links or that sort of thing, so that you
don't need to fully impersonate their account to do so.
</p>
</div>
<div class="field">
<label class="label" for="about_me">About Me</label>
<textarea class="textarea mb-4"
cols="80" rows="4"
name="about_me" id="about_me">{{.User.GetProfileField "about_me"}}</textarea>
</div>
<div class="field">
<label class="label" for="interests">My Interests</label>
<textarea class="textarea mb-4"
cols="80" rows="4"
name="interests" id="interests">{{.User.GetProfileField "interests"}}</textarea>
</div>
<div class="field">
<label class="label" for="music_movies">Music/Bands/Movies</label>
<textarea class="textarea mb-4"
cols="80" rows="4"
name="music_movies" id="music_movies">{{.User.GetProfileField "music_movies"}}</textarea>
</div>
<div class="field has-text-centered">
<button type="submit" class="button is-success">
Save Changes
</button>
</div>
{{else if eq .Intent "impersonate"}}
<div class="block content">
<h3>With great power...</h3>