* Identify adult content labels and handle them more specifically * Change adult content defaults to more conservative settings * Add an adultcontentenabled override that prohibits access on iOS * Improve usability of the content hider * Fix lint
409 lines
9.6 KiB
TypeScript
409 lines
9.6 KiB
TypeScript
import {
|
|
AppBskyActorDefs,
|
|
AppBskyEmbedRecordWithMedia,
|
|
AppBskyEmbedRecord,
|
|
AppBskyEmbedImages,
|
|
AppBskyEmbedExternal,
|
|
} from '@atproto/api'
|
|
import {
|
|
CONFIGURABLE_LABEL_GROUPS,
|
|
ILLEGAL_LABEL_GROUP,
|
|
ALWAYS_FILTER_LABEL_GROUP,
|
|
ALWAYS_WARN_LABEL_GROUP,
|
|
UNKNOWN_LABEL_GROUP,
|
|
} from './const'
|
|
import {
|
|
Label,
|
|
LabelValGroup,
|
|
ModerationBehaviorCode,
|
|
PostModeration,
|
|
ProfileModeration,
|
|
PostLabelInfo,
|
|
ProfileLabelInfo,
|
|
} from './types'
|
|
import {RootStoreModel} from 'state/index'
|
|
|
|
type Embed =
|
|
| AppBskyEmbedRecord.View
|
|
| AppBskyEmbedImages.View
|
|
| AppBskyEmbedExternal.View
|
|
| AppBskyEmbedRecordWithMedia.View
|
|
| {$type: string; [k: string]: unknown}
|
|
|
|
export function getLabelValueGroup(labelVal: string): LabelValGroup {
|
|
let id: keyof typeof CONFIGURABLE_LABEL_GROUPS
|
|
for (id in CONFIGURABLE_LABEL_GROUPS) {
|
|
if (ILLEGAL_LABEL_GROUP.values.includes(labelVal)) {
|
|
return ILLEGAL_LABEL_GROUP
|
|
}
|
|
if (ALWAYS_FILTER_LABEL_GROUP.values.includes(labelVal)) {
|
|
return ALWAYS_FILTER_LABEL_GROUP
|
|
}
|
|
if (ALWAYS_WARN_LABEL_GROUP.values.includes(labelVal)) {
|
|
return ALWAYS_WARN_LABEL_GROUP
|
|
}
|
|
if (CONFIGURABLE_LABEL_GROUPS[id].values.includes(labelVal)) {
|
|
return CONFIGURABLE_LABEL_GROUPS[id]
|
|
}
|
|
}
|
|
return UNKNOWN_LABEL_GROUP
|
|
}
|
|
|
|
export function getPostModeration(
|
|
store: RootStoreModel,
|
|
postInfo: PostLabelInfo,
|
|
): PostModeration {
|
|
const accountPref = store.preferences.getLabelPreference(
|
|
postInfo.accountLabels,
|
|
)
|
|
const profilePref = store.preferences.getLabelPreference(
|
|
postInfo.profileLabels,
|
|
)
|
|
const postPref = store.preferences.getLabelPreference(postInfo.postLabels)
|
|
|
|
// avatar
|
|
let avatar = {
|
|
warn: accountPref.pref === 'hide' || accountPref.pref === 'warn',
|
|
blur:
|
|
postInfo.isBlocking ||
|
|
accountPref.pref === 'hide' ||
|
|
accountPref.pref === 'warn' ||
|
|
profilePref.pref === 'hide' ||
|
|
profilePref.pref === 'warn',
|
|
}
|
|
|
|
// hide no-override cases
|
|
if (accountPref.pref === 'hide' && accountPref.desc.id === 'illegal') {
|
|
return hidePostNoOverride(accountPref.desc.warning)
|
|
}
|
|
if (profilePref.pref === 'hide' && profilePref.desc.id === 'illegal') {
|
|
return hidePostNoOverride(profilePref.desc.warning)
|
|
}
|
|
if (postPref.pref === 'hide' && postPref.desc.id === 'illegal') {
|
|
return hidePostNoOverride(postPref.desc.warning)
|
|
}
|
|
|
|
// hide cases
|
|
if (postInfo.isBlocking) {
|
|
return {
|
|
avatar,
|
|
list: hide('Post from an account you blocked.'),
|
|
thread: hide('Post from an account you blocked.'),
|
|
view: warn('Post from an account you blocked.'),
|
|
}
|
|
}
|
|
if (postInfo.isBlockedBy) {
|
|
return {
|
|
avatar,
|
|
list: hide('Post from an account that has blocked you.'),
|
|
thread: hide('Post from an account that has blocked you.'),
|
|
view: warn('Post from an account that has blocked you.'),
|
|
}
|
|
}
|
|
if (accountPref.pref === 'hide') {
|
|
return {
|
|
avatar,
|
|
list: hide(accountPref.desc.warning),
|
|
thread: hide(accountPref.desc.warning),
|
|
view: warn(accountPref.desc.warning),
|
|
}
|
|
}
|
|
if (profilePref.pref === 'hide') {
|
|
return {
|
|
avatar,
|
|
list: hide(profilePref.desc.warning),
|
|
thread: hide(profilePref.desc.warning),
|
|
view: warn(profilePref.desc.warning),
|
|
}
|
|
}
|
|
if (postPref.pref === 'hide') {
|
|
return {
|
|
avatar,
|
|
list: hide(postPref.desc.warning),
|
|
thread: hide(postPref.desc.warning),
|
|
view: warn(postPref.desc.warning),
|
|
}
|
|
}
|
|
|
|
// muting
|
|
if (postInfo.isMuted) {
|
|
return {
|
|
avatar,
|
|
list: hide('Post from an account you muted.'),
|
|
thread: warn('Post from an account you muted.'),
|
|
view: warn('Post from an account you muted.'),
|
|
}
|
|
}
|
|
|
|
// warning cases
|
|
if (postPref.pref === 'warn') {
|
|
if (postPref.desc.isAdultImagery) {
|
|
return {
|
|
avatar,
|
|
list: warnImages(postPref.desc.warning),
|
|
thread: warnImages(postPref.desc.warning),
|
|
view: warnImages(postPref.desc.warning),
|
|
}
|
|
}
|
|
return {
|
|
avatar,
|
|
list: warnContent(postPref.desc.warning),
|
|
thread: warnContent(postPref.desc.warning),
|
|
view: warnContent(postPref.desc.warning),
|
|
}
|
|
}
|
|
if (accountPref.pref === 'warn') {
|
|
return {
|
|
avatar,
|
|
list: warnContent(accountPref.desc.warning),
|
|
thread: warnContent(accountPref.desc.warning),
|
|
view: warnContent(accountPref.desc.warning),
|
|
}
|
|
}
|
|
|
|
return {
|
|
avatar,
|
|
list: show(),
|
|
thread: show(),
|
|
view: show(),
|
|
}
|
|
}
|
|
|
|
export function mergePostModerations(
|
|
moderations: PostModeration[],
|
|
): PostModeration {
|
|
const merged: PostModeration = {
|
|
avatar: {warn: false, blur: false},
|
|
list: show(),
|
|
thread: show(),
|
|
view: show(),
|
|
}
|
|
for (const mod of moderations) {
|
|
if (mod.list.behavior === ModerationBehaviorCode.Hide) {
|
|
merged.list = mod.list
|
|
}
|
|
if (mod.thread.behavior === ModerationBehaviorCode.Hide) {
|
|
merged.thread = mod.thread
|
|
}
|
|
if (mod.view.behavior === ModerationBehaviorCode.Hide) {
|
|
merged.view = mod.view
|
|
}
|
|
}
|
|
return merged
|
|
}
|
|
|
|
export function getProfileModeration(
|
|
store: RootStoreModel,
|
|
profileInfo: ProfileLabelInfo,
|
|
): ProfileModeration {
|
|
const accountPref = store.preferences.getLabelPreference(
|
|
profileInfo.accountLabels,
|
|
)
|
|
const profilePref = store.preferences.getLabelPreference(
|
|
profileInfo.profileLabels,
|
|
)
|
|
|
|
// avatar
|
|
let avatar = {
|
|
warn: accountPref.pref === 'hide' || accountPref.pref === 'warn',
|
|
blur:
|
|
profileInfo.isBlocking ||
|
|
accountPref.pref === 'hide' ||
|
|
accountPref.pref === 'warn' ||
|
|
profilePref.pref === 'hide' ||
|
|
profilePref.pref === 'warn',
|
|
}
|
|
|
|
// hide no-override cases
|
|
if (accountPref.pref === 'hide' && accountPref.desc.id === 'illegal') {
|
|
return hideProfileNoOverride(accountPref.desc.warning)
|
|
}
|
|
if (profilePref.pref === 'hide' && profilePref.desc.id === 'illegal') {
|
|
return hideProfileNoOverride(profilePref.desc.warning)
|
|
}
|
|
|
|
// hide cases
|
|
if (accountPref.pref === 'hide') {
|
|
return {
|
|
avatar,
|
|
list: hide(accountPref.desc.warning),
|
|
view: hide(accountPref.desc.warning),
|
|
}
|
|
}
|
|
if (profilePref.pref === 'hide') {
|
|
return {
|
|
avatar,
|
|
list: hide(profilePref.desc.warning),
|
|
view: hide(profilePref.desc.warning),
|
|
}
|
|
}
|
|
|
|
// warn cases
|
|
if (accountPref.pref === 'warn') {
|
|
return {
|
|
avatar,
|
|
list:
|
|
profileInfo.isBlocking || profileInfo.isBlockedBy
|
|
? hide('Blocked account')
|
|
: warn(accountPref.desc.warning),
|
|
view: warn(accountPref.desc.warning),
|
|
}
|
|
}
|
|
// we don't warn for this
|
|
// if (profilePref.pref === 'warn') {
|
|
// return {
|
|
// avatar,
|
|
// list: warn(profilePref.desc.warning),
|
|
// view: warn(profilePref.desc.warning),
|
|
// }
|
|
// }
|
|
|
|
return {
|
|
avatar,
|
|
list: profileInfo.isBlocking ? hide('Blocked account') : show(),
|
|
view: show(),
|
|
}
|
|
}
|
|
|
|
export function getProfileViewBasicLabelInfo(
|
|
profile: AppBskyActorDefs.ProfileViewBasic,
|
|
): ProfileLabelInfo {
|
|
return {
|
|
accountLabels: filterAccountLabels(profile.labels),
|
|
profileLabels: filterProfileLabels(profile.labels),
|
|
isMuted: profile.viewer?.muted || false,
|
|
isBlocking: !!profile.viewer?.blocking || false,
|
|
}
|
|
}
|
|
|
|
export function getEmbedLabels(embed?: Embed): Label[] {
|
|
if (!embed) {
|
|
return []
|
|
}
|
|
if (
|
|
AppBskyEmbedRecord.isView(embed) &&
|
|
AppBskyEmbedRecord.isViewRecord(embed.record)
|
|
) {
|
|
return embed.record.labels || []
|
|
}
|
|
return []
|
|
}
|
|
|
|
export function getEmbedMuted(embed?: Embed): boolean {
|
|
if (!embed) {
|
|
return false
|
|
}
|
|
if (
|
|
AppBskyEmbedRecord.isView(embed) &&
|
|
AppBskyEmbedRecord.isViewRecord(embed.record)
|
|
) {
|
|
return !!embed.record.author.viewer?.muted
|
|
}
|
|
return false
|
|
}
|
|
|
|
export function getEmbedBlocking(embed?: Embed): boolean {
|
|
if (!embed) {
|
|
return false
|
|
}
|
|
if (
|
|
AppBskyEmbedRecord.isView(embed) &&
|
|
AppBskyEmbedRecord.isViewRecord(embed.record)
|
|
) {
|
|
return !!embed.record.author.viewer?.blocking
|
|
}
|
|
return false
|
|
}
|
|
|
|
export function getEmbedBlockedBy(embed?: Embed): boolean {
|
|
if (!embed) {
|
|
return false
|
|
}
|
|
if (
|
|
AppBskyEmbedRecord.isView(embed) &&
|
|
AppBskyEmbedRecord.isViewRecord(embed.record)
|
|
) {
|
|
return !!embed.record.author.viewer?.blockedBy
|
|
}
|
|
return false
|
|
}
|
|
|
|
export function filterAccountLabels(labels?: Label[]): Label[] {
|
|
if (!labels) {
|
|
return []
|
|
}
|
|
return labels.filter(
|
|
label => !label.uri.endsWith('/app.bsky.actor.profile/self'),
|
|
)
|
|
}
|
|
|
|
export function filterProfileLabels(labels?: Label[]): Label[] {
|
|
if (!labels) {
|
|
return []
|
|
}
|
|
return labels.filter(label =>
|
|
label.uri.endsWith('/app.bsky.actor.profile/self'),
|
|
)
|
|
}
|
|
|
|
// internal methods
|
|
// =
|
|
|
|
function show() {
|
|
return {
|
|
behavior: ModerationBehaviorCode.Show,
|
|
}
|
|
}
|
|
|
|
function hidePostNoOverride(reason: string) {
|
|
return {
|
|
avatar: {warn: true, blur: true},
|
|
list: hideNoOverride(reason),
|
|
thread: hideNoOverride(reason),
|
|
view: hideNoOverride(reason),
|
|
}
|
|
}
|
|
|
|
function hideProfileNoOverride(reason: string) {
|
|
return {
|
|
avatar: {warn: true, blur: true},
|
|
list: hideNoOverride(reason),
|
|
view: hideNoOverride(reason),
|
|
}
|
|
}
|
|
|
|
function hideNoOverride(reason: string) {
|
|
return {
|
|
behavior: ModerationBehaviorCode.Hide,
|
|
reason,
|
|
noOverride: true,
|
|
}
|
|
}
|
|
|
|
function hide(reason: string) {
|
|
return {
|
|
behavior: ModerationBehaviorCode.Hide,
|
|
reason,
|
|
}
|
|
}
|
|
|
|
function warn(reason: string) {
|
|
return {
|
|
behavior: ModerationBehaviorCode.Warn,
|
|
reason,
|
|
}
|
|
}
|
|
|
|
function warnContent(reason: string) {
|
|
return {
|
|
behavior: ModerationBehaviorCode.WarnContent,
|
|
reason,
|
|
}
|
|
}
|
|
|
|
function warnImages(reason: string) {
|
|
return {
|
|
behavior: ModerationBehaviorCode.WarnImages,
|
|
reason,
|
|
}
|
|
}
|