Search terms and admin features
This commit is contained in:
parent
2f997dfee0
commit
a0320714c4
|
@ -1,6 +1,7 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
@ -9,6 +10,7 @@ import (
|
||||||
"code.nonshy.com/nonshy/website/pkg/log"
|
"code.nonshy.com/nonshy/website/pkg/log"
|
||||||
"code.nonshy.com/nonshy/website/pkg/models"
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||||||
"code.nonshy.com/nonshy/website/pkg/session"
|
"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/templates"
|
||||||
"code.nonshy.com/nonshy/website/pkg/worker"
|
"code.nonshy.com/nonshy/website/pkg/worker"
|
||||||
)
|
)
|
||||||
|
@ -50,7 +52,8 @@ func Search() http.HandlerFunc {
|
||||||
ageMin, ageMax = ageMax, ageMin
|
ageMin, ageMax = ageMax, ageMin
|
||||||
}
|
}
|
||||||
|
|
||||||
search := models.ParseSearchString(searchTerm)
|
rawSearch := models.ParseSearchString(searchTerm)
|
||||||
|
search, restricted := spam.RestrictSearchTerms(rawSearch)
|
||||||
|
|
||||||
// Get current user.
|
// Get current user.
|
||||||
currentUser, err := session.CurrentUser(r)
|
currentUser, err := session.CurrentUser(r)
|
||||||
|
@ -60,6 +63,32 @@ func Search() http.HandlerFunc {
|
||||||
return
|
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
|
// Geolocation/Who's Nearby: if the current user uses GeoIP, update
|
||||||
// their coordinates now.
|
// their coordinates now.
|
||||||
myLocation, err := models.RefreshGeoIP(currentUser.ID, r)
|
myLocation, err := models.RefreshGeoIP(currentUser.ID, r)
|
||||||
|
@ -160,6 +189,9 @@ func Search() http.HandlerFunc {
|
||||||
"LikedSearch": likedSearch,
|
"LikedSearch": likedSearch,
|
||||||
"Sort": sort,
|
"Sort": sort,
|
||||||
|
|
||||||
|
// Restricted Search errors.
|
||||||
|
"RestrictedSearchError": restricted,
|
||||||
|
|
||||||
// Photo counts mapped to users
|
// Photo counts mapped to users
|
||||||
"PhotoCountMap": models.MapPhotoCounts(users),
|
"PhotoCountMap": models.MapPhotoCounts(users),
|
||||||
|
|
||||||
|
|
|
@ -122,6 +122,34 @@ func UserActions() http.HandlerFunc {
|
||||||
session.FlashError(w, r, "Error getting blocklist insights: %s", err)
|
session.FlashError(w, r, "Error getting blocklist insights: %s", err)
|
||||||
}
|
}
|
||||||
vars["BlocklistInsights"] = insights
|
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":
|
case "impersonate":
|
||||||
// Scope check.
|
// Scope check.
|
||||||
if !currentUser.HasAdminScope(config.ScopeUserImpersonate) {
|
if !currentUser.HasAdminScope(config.ScopeUserImpersonate) {
|
||||||
|
|
|
@ -3,6 +3,8 @@ package spam
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"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
|
// 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
|
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>
|
</li>
|
||||||
|
|
||||||
<p class="menu-label">Admin Actions</p>
|
<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>
|
<li>
|
||||||
<a href="/admin/user-action?intent=impersonate&user_id={{.User.ID}}">
|
<a href="/admin/user-action?intent=impersonate&user_id={{.User.ID}}">
|
||||||
<span class="icon"><i class="fa fa-ghost"></i></span>
|
<span class="icon"><i class="fa fa-ghost"></i></span>
|
||||||
|
|
|
@ -69,6 +69,18 @@
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{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="block">
|
||||||
|
|
||||||
<div class="card nonshy-collapsible-mobile">
|
<div class="card nonshy-collapsible-mobile">
|
||||||
|
|
|
@ -264,6 +264,14 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="has-text-right">
|
||||||
|
<strong>Date:</strong>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{.CreatedAt.Format "2006-01-02 15:04:05 MST"}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|
|
@ -22,6 +22,9 @@
|
||||||
{{if eq .Intent "impersonate"}}
|
{{if eq .Intent "impersonate"}}
|
||||||
<i class="mr-2 fa fa-ghost"></i>
|
<i class="mr-2 fa fa-ghost"></i>
|
||||||
Impersonate User
|
Impersonate User
|
||||||
|
{{else if eq .Intent "essays"}}
|
||||||
|
<i class="mr-2 fa fa-pencil"></i>
|
||||||
|
Edit Profile Text
|
||||||
{{else if eq .Intent "ban"}}
|
{{else if eq .Intent "ban"}}
|
||||||
<i class="mr-2 fa fa-ban"></i>
|
<i class="mr-2 fa fa-ban"></i>
|
||||||
Ban User
|
Ban User
|
||||||
|
@ -145,6 +148,41 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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"}}
|
{{else if eq .Intent "impersonate"}}
|
||||||
<div class="block content">
|
<div class="block content">
|
||||||
<h3>With great power...</h3>
|
<h3>With great power...</h3>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user