diff --git a/pkg/controller/account/dashboard.go b/pkg/controller/account/dashboard.go
index c3eefd4..1443036 100644
--- a/pkg/controller/account/dashboard.go
+++ b/pkg/controller/account/dashboard.go
@@ -43,10 +43,21 @@ func Dashboard() http.HandlerFunc {
notifMap := models.MapNotifications(notifs)
models.SetUserRelationshipsInNotifications(currentUser, notifs)
+ // Restricted profile warnings.
+ var (
+ isShyUser = currentUser.IsShy()
+ photoTypes = currentUser.DistinctPhotoTypes()
+ _, hasPublic = photoTypes[models.PhotoPublic]
+ )
+
var vars = map[string]interface{}{
"Notifications": notifs,
"NotifMap": notifMap,
"Pager": pager,
+
+ // Show a warning to 'restricted' profiles who are especially private.
+ "IsShyUser": isShyUser,
+ "HasPublicPhoto": hasPublic,
}
if err := tmpl.Execute(w, r, vars); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
diff --git a/pkg/controller/chat/chat.go b/pkg/controller/chat/chat.go
index 3756c73..d7b4135 100644
--- a/pkg/controller/chat/chat.go
+++ b/pkg/controller/chat/chat.go
@@ -39,8 +39,21 @@ func Landing() http.HandlerFunc {
}
// Are they logging into the chat room?
- var intent = r.FormValue("intent")
+ var (
+ intent = r.FormValue("intent")
+ isShy = currentUser.IsShy()
+ )
if intent == "join" {
+ // If we are shy, block chat for now.
+ if isShy {
+ session.FlashError(w, r,
+ "You have a Shy Account and are not allowed in the chat room at this time where our non-shy members may "+
+ "be on camera.",
+ )
+ templates.Redirect(w, "/chat")
+ return
+ }
+
// Get our Chat JWT secret.
var (
secret = []byte(config.Current.BareRTC.JWTSecret)
@@ -91,7 +104,8 @@ func Landing() http.HandlerFunc {
}
var vars = map[string]interface{}{
- "ChatAPI": strings.TrimSuffix(config.Current.BareRTC.URL, "/") + "/api/statistics",
+ "ChatAPI": strings.TrimSuffix(config.Current.BareRTC.URL, "/") + "/api/statistics",
+ "IsShyUser": isShy,
}
if err := tmpl.Execute(w, r, vars); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
diff --git a/pkg/controller/inbox/compose.go b/pkg/controller/inbox/compose.go
index 7419943..08babcc 100644
--- a/pkg/controller/inbox/compose.go
+++ b/pkg/controller/inbox/compose.go
@@ -69,6 +69,18 @@ func Compose() http.HandlerFunc {
return
}
+ // On GET request (come from a user profile page):
+ // Do not allow a shy user to initiate DMs with a non-shy one.
+ var (
+ imShy = currentUser.IsShy()
+ isShyFrom = currentUser.IsShyFrom(user) || (imShy && !models.AreFriends(currentUser.ID, user.ID))
+ )
+ if imShy && isShyFrom {
+ session.FlashError(w, r, "You have a Shy Account and can not initiate Direct Messages with a non-shy member.")
+ templates.Redirect(w, "/u/"+user.Username)
+ return
+ }
+
var vars = map[string]interface{}{
"User": user,
}
diff --git a/pkg/controller/photo/site_gallery.go b/pkg/controller/photo/site_gallery.go
index 45dd8a2..9758233 100644
--- a/pkg/controller/photo/site_gallery.go
+++ b/pkg/controller/photo/site_gallery.go
@@ -54,6 +54,9 @@ func SiteGallery() http.HandlerFunc {
session.FlashError(w, r, "Unexpected error: couldn't get CurrentUser")
}
+ // Is the current viewer shy?
+ var isShy = currentUser.IsShy()
+
// Get the page of photos.
pager := &models.Pagination{
Page: 1,
@@ -61,7 +64,12 @@ func SiteGallery() http.HandlerFunc {
Sort: sort,
}
pager.ParsePage(r)
- photos, err := models.PaginateGalleryPhotos(currentUser, filterExplicit, filterVisibility, adminView, pager)
+ photos, _ := models.PaginateGalleryPhotos(currentUser, models.Gallery{
+ Explicit: filterExplicit,
+ Visibility: filterVisibility,
+ AdminView: adminView,
+ ShyView: isShy,
+ }, pager)
// Bulk load the users associated with these photos.
var userIDs = []uint64{}
@@ -95,6 +103,9 @@ func SiteGallery() http.HandlerFunc {
"FilterExplicit": filterExplicit,
"FilterVisibility": filterVisibility,
"AdminView": adminView,
+
+ // Is the current user shy?
+ "IsShyUser": isShy,
}
if err := tmpl.Execute(w, r, vars); err != nil {
diff --git a/pkg/controller/photo/user_gallery.go b/pkg/controller/photo/user_gallery.go
index ae0157d..d87d039 100644
--- a/pkg/controller/photo/user_gallery.go
+++ b/pkg/controller/photo/user_gallery.go
@@ -43,7 +43,31 @@ func UserPhotos() http.HandlerFunc {
if err != nil {
session.FlashError(w, r, "Unexpected error: couldn't get CurrentUser")
}
- var isOwnPhotos = currentUser.ID == user.ID
+ var (
+ isOwnPhotos = currentUser.ID == user.ID
+ isShy = currentUser.IsShy()
+ isShyFrom = currentUser.IsShyFrom(user) || (isShy && !models.AreFriends(currentUser.ID, user.ID))
+ )
+
+ // Bail early if we are shy from this user.
+ if isShy && isShyFrom {
+ var vars = map[string]interface{}{
+ "IsOwnPhotos": currentUser.ID == user.ID,
+ "IsShyUser": isShy,
+ "IsShyFrom": isShyFrom,
+ // "IsMyPrivateUnlockedFor": isGranted, // have WE granted THIS USER to see our private pics?
+ // "AreWeGrantedPrivate": isGrantee, // have THEY granted US private photo access.
+ "User": user,
+ "Photos": []*models.Photo{},
+ "PhotoCount": models.CountPhotos(user.ID),
+ "Pager": models.Pagination{},
+ }
+
+ if err := tmpl.Execute(w, r, vars); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ return
+ }
// Is either one blocking?
if models.IsBlocking(currentUser.ID, user.ID) && !currentUser.IsAdmin {
@@ -107,6 +131,8 @@ func UserPhotos() http.HandlerFunc {
var vars = map[string]interface{}{
"IsOwnPhotos": currentUser.ID == user.ID,
+ "IsShyUser": isShy,
+ "IsShyFrom": isShyFrom,
"IsMyPrivateUnlockedFor": isGranted, // have WE granted THIS USER to see our private pics?
"AreWeGrantedPrivate": isGrantee, // have THEY granted US private photo access.
"User": user,
diff --git a/pkg/models/pagination.go b/pkg/models/pagination.go
index 2d2571d..9b67adf 100644
--- a/pkg/models/pagination.go
+++ b/pkg/models/pagination.go
@@ -85,6 +85,9 @@ func (p *Pagination) Iter() []Page {
}
func (p *Pagination) Pages() int {
+ if p.PerPage == 0 {
+ return 0
+ }
return int(math.Ceil(float64(p.Total) / float64(p.PerPage)))
}
diff --git a/pkg/models/photo.go b/pkg/models/photo.go
index e0dd5ae..8322710 100644
--- a/pkg/models/photo.go
+++ b/pkg/models/photo.go
@@ -150,15 +150,57 @@ func CountExplicitPhotos(userID uint64, visibility []PhotoVisibility) (int64, er
return count, result.Error
}
+// DistinctPhotoTypes returns types of photos the user has: a set of public, friends, or private.
+//
+// The result is cached on the User the first time it's queried.
+func (u *User) DistinctPhotoTypes() (result map[PhotoVisibility]struct{}) {
+ if u.cachePhotoTypes != nil {
+ return u.cachePhotoTypes
+ }
+
+ result = map[PhotoVisibility]struct{}{}
+
+ var results = []*Photo{}
+ query := DB.Model(&Photo{}).
+ Select("DISTINCT photos.visibility").
+ Where("user_id = ?", u.ID).
+ Group("photos.visibility").
+ Find(&results)
+ if query.Error != nil {
+ log.Error("User.DistinctPhotoTypes(%s): %s", u.Username, query.Error)
+ return
+ }
+
+ for _, row := range results {
+ log.Warn("DistinctPhotoTypes(%s): got %+v", u.Username, row)
+ result[row.Visibility] = struct{}{}
+ }
+
+ u.cachePhotoTypes = result
+ return
+}
+
+// Gallery config for the main Gallery paginator.
+type Gallery struct {
+ Explicit string // Explicit filter
+ Visibility string // Visibility filter
+ AdminView bool // Show all images
+ ShyView bool // Current user is shy (self/friends only)
+}
+
/*
PaginateGalleryPhotos gets a page of all public user photos for the site gallery.
Admin view returns ALL photos regardless of Gallery status.
*/
-func PaginateGalleryPhotos(user *User, filterExplicit, filterVisibility string, adminView bool, pager *Pagination) ([]*Photo, error) {
+func PaginateGalleryPhotos(user *User, conf Gallery, pager *Pagination) ([]*Photo, error) {
var (
- p = []*Photo{}
- query *gorm.DB
+ filterExplicit = conf.Explicit
+ filterVisibility = conf.Visibility
+ adminView = conf.AdminView
+ isShy = conf.ShyView
+ p = []*Photo{}
+ query *gorm.DB
// Get the user ID and their preferences.
userID = user.ID
@@ -177,17 +219,31 @@ func PaginateGalleryPhotos(user *User, filterExplicit, filterVisibility string,
// Include ourself in our friend IDs.
friendIDs = append(friendIDs, userID)
- // You can see friends' Friend photos but only public for non-friends.
- wheres = append(wheres,
- "((user_id IN ? AND visibility IN ?) OR "+
- "(user_id IN ? AND visibility IN ?) OR "+
- "(user_id NOT IN ? AND visibility = ?))",
- )
- placeholders = append(placeholders,
- friendIDs, PhotoVisibilityFriends,
- privateUserIDs, PhotoVisibilityAll,
- friendIDs, PhotoPublic,
- )
+ // Whose photos can you see on the Site Gallery?
+ if isShy {
+ // Shy users can only see their Friends photos (public or friends visibility)
+ // and any Private photos to whom they were granted access.
+ wheres = append(wheres,
+ "((user_id IN ? AND visibility IN ?) OR "+
+ "(user_id IN ? AND visibility IN ?))",
+ )
+ placeholders = append(placeholders,
+ friendIDs, PhotoVisibilityFriends,
+ privateUserIDs, PhotoVisibilityAll,
+ )
+ } else {
+ // You can see friends' Friend photos but only public for non-friends.
+ wheres = append(wheres,
+ "((user_id IN ? AND visibility IN ?) OR "+
+ "(user_id IN ? AND visibility IN ?) OR "+
+ "(user_id NOT IN ? AND visibility = ?))",
+ )
+ placeholders = append(placeholders,
+ friendIDs, PhotoVisibilityFriends,
+ privateUserIDs, PhotoVisibilityAll,
+ friendIDs, PhotoPublic,
+ )
+ }
// Gallery photos only.
wheres = append(wheres, "gallery = ?")
diff --git a/pkg/models/user.go b/pkg/models/user.go
index 53dfe50..9d751ed 100644
--- a/pkg/models/user.go
+++ b/pkg/models/user.go
@@ -37,6 +37,9 @@ type User struct {
// Current user's relationship to this user -- not stored in DB.
UserRelationship UserRelationship `gorm:"-"`
+
+ // Caches
+ cachePhotoTypes map[PhotoVisibility]struct{}
}
type UserVisibility string
@@ -132,6 +135,49 @@ func FindUser(username string) (*User, error) {
return u, result.Error
}
+// IsShy returns whether the user might have an "empty" profile from the perspective of anybody.
+//
+// An empty profile means their profile is Private or else ALL of their photos are non-public; so that
+// somebody viewing their page might see nothing at all from them and consider them a "blank" profile.
+func (u *User) IsShy() bool {
+ // Non-certified users are considered empty.
+ if !u.Certified {
+ return true
+ }
+
+ // Private profile automatically applies.
+ if u.Visibility == UserVisibilityPrivate {
+ return true
+ }
+
+ // If ALL of our photos are non-public, that counts too.
+ var photoTypes = u.DistinctPhotoTypes()
+ if _, ok := photoTypes[PhotoPublic]; !ok {
+ log.Info("IsEmptyProfile: true because visibilities %+v did not include public", photoTypes)
+ return true
+ }
+
+ return false
+}
+
+// IsShyFrom tells whether the user is shy from the perspective of the other user.
+//
+// That is, depending on our profile visibility and friendship status.
+func (u *User) IsShyFrom(other *User) bool {
+ // If we are not a private profile, we're shy from nobody.
+ if u.Visibility != UserVisibilityPrivate {
+ return false
+ }
+
+ // Not shy from our friends.
+ if AreFriends(u.ID, other.ID) {
+ return false
+ }
+
+ // Our profile must be private & we are not friended, we are shy.
+ return true
+}
+
// UserSearch config.
type UserSearch struct {
EmailOrUsername string
diff --git a/web/templates/account/dashboard.html b/web/templates/account/dashboard.html
index 1c1f3c8..d343e42 100644
--- a/web/templates/account/dashboard.html
+++ b/web/templates/account/dashboard.html
@@ -68,6 +68,71 @@
{{end}}
+
+ {{if and .CurrentUser.Certified .IsShyUser}}
+
+
+
+
+ Your profile page is too private
+
+
+
+
+
+ You are considered to be a Shy Account because your profile and
+ photos are all set to Private or Friends-only visibility, so that to other members
+ of {{PrettyTitle}} you appear like a blank, faceless profile.
+
+
+
+ While in this restricted state, you are grouped into a cohort with other members who
+ are as shy as you are and have limited contact options to connect with our other,
+ {{PrettyTitle}} members who are sharing their nudes on public.
+
+
+
+ Click here to learn more about your Shy Account. To
+ remedy this, please see the following steps:
+
+ You have a Shy Account and you may not enter
+ the chat room at this time, where our {{PrettyTitle}} members may be sharing their cameras. You are
+ sharing no public photos with the community, so you get limited access to ours.
+ Learn more about how to resolve this issue.
+
+ {{end}}
+
{{PrettyTitle}} has a new chat room! Come and check it out. It features some public rooms, direct
@@ -60,6 +69,10 @@
yet for Safari. Use Firefox or Chrome (or other Chromium browser of your choice) for better odds
of video support.
+
+
+ Unfortunately, the chat room does not work on iPhones or iPads at this time.
+
+ One of the things that {{PrettyTitle}} wishes to avoid is the dreaded "blank profile"
+ that slides into our DMs and gets creepy and weird on us. You are encouraged to participate
+ on this site and share at least one public photo with the community. You may opt to have
+ only "G-rated face pics" on public and nudes on private, or keep your face on private and
+ share some body shots with your face cropped out on public - but share at least one good
+ picture on public.
+
+
+
+ When your profile page or photos are all set to Private or Friends-only, you will
+ be considered to have a Shy Account.
+ A Shy Account can still interact on the forums but will have limited options to
+ interact with non-restricted ({{PrettyTitle}}) members.
+
+
+
What restrictions apply to Shy Accounts?
+
+
+ The limits placed on Shy Accounts are:
+
+
+
+
+ The Site Gallery will only show you pictures
+ of people equally as shy as you are. That is, you may see your own pictures and those of
+ Friends you have added, but you don't see public shares of {{PrettyTitle}} people
+ who aren't your friends.
+
+
+ Messages: you may slide into the DMs only
+ of other shy members but you can not initiate DMs with a {{PrettyTitle}} one who is not on
+ your Friends list. At their own discretion, they may initiate a chat with you and then you can reply to them.
+
+
+ You can view anybody's Profile Page but you
+ can not see a {{PrettyTitle}} account's Photo Gallery unless they are
+ your Friend or have shared their private pictures with you.
+
+
+ You can not join the Chat Room. You guys
+ may soon get your own chat room, though. Many of us {{PrettyTitle}} nudists would not
+ enjoy our webcams being watched by blank profiles.
+
+
+
+
+ The idea is to keep the shy members isolated from the non-shy ones. We nudists are sharing
+ what we can and we don't want creepers to be ogling our nudes and not sharing anything in
+ return. If all your pics are private, you look like a blank profile to us - and you will be
+ kept with the other blank profiles until you choose to participate.
+
+
+
What can Shy Accounts do?
+
+
+ There are still a lot things you can do with your certified
+ but Shy Account:
+
+
+
+
+ You can still participate on the Forums and meet new friends
+ that way - by contributing to discussions, ideally.
+
+
+ You can send a Friend request to anybody and if they accept you
+ can see their Photo Gallery and pictures appear in the Site Gallery.
+
+
+ You can send DMs to other shy people like yourself, and reply
+ to DMs that were sent by anybody who messages you first.
+
+
+ You can browse the Member Directory and view public
+ profile pages and send friend requests to whoever.
+
+
+
+
How do I fix it?
+
+
+ Leaving Shy Account territory is easy:
+
+
+
+
Don't have your profile page set to private. Only logged-in, certified members can see your page, anyway!
+
+ Have at least one public picture to share with the class. Ideally, it will be your profile picture that
+ shows your face, but we'll settle for a good headless body shot. We're all sharing our nudes here, we'd like it if you
+ participated as well.
+
+
+
+
+ If you are new to all of this, here are some ideas how you can manage your
+ photo gallery to have at least one public picture to share:
+
+
+
+
You could have a single, "G-rated" face pic as your Public profile picture, and have the others on Friends-only or Private.
+
+ You could upload all your "G-rated" face pics as Public, and have nudes (with your face cropped out if you need) on Friends-only
+ or Private.
+
+
+ You could have a non-public profile pic along with "anonymized" nudes on Public, full nudes w/ face on Friends-only, and
+ sexual stuff on Private that you unlock on a per-person basis.
+
+
+ You can opt-out of the Site Gallery by un-checking the Gallery box on the upload page. Your public
+ photos then would only been seen if somebody clicks through your profile page to see your gallery.
+