Search terms and admin features
This commit is contained in:
parent
2f997dfee0
commit
a0320714c4
|
@ -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),
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue
Block a user