mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
feat(enforcement): record decisions and send notices (#3492)
This commit is contained in:
1 parent
da1842ecab
commit
f5c30fdf8f
383 files changed
+29692
-4139
No files matched your search
@@ -46,6 +46,8 @@ export const AdminACLs = {
|
||||
BULK_UPDATE_USER_FLAGS: 'bulk:update:user_flags',
|
||||
DISCOVERY_REMOVE: 'discovery:remove',
|
||||
DISCOVERY_REVIEW: 'discovery:review',
|
||||
ENFORCEMENT_ANNOTATE: 'enforcement:annotate',
|
||||
ENFORCEMENT_VIEW: 'enforcement:view',
|
||||
GATEWAY_MEMORY_STATS: 'gateway:memory_stats',
|
||||
GATEWAY_RELOAD_ALL: 'gateway:reload_all',
|
||||
GIFT_CODES_GENERATE: 'gift_codes:generate',
|
||||
|
||||
@@ -67,6 +67,11 @@ export const APIErrorCodes = {
|
||||
EMAIL_UNAVAILABLE_ON_INSTANCE: 'EMAIL_UNAVAILABLE_ON_INSTANCE',
|
||||
EMAIL_VERIFICATION_REQUIRED: 'EMAIL_VERIFICATION_REQUIRED',
|
||||
DIRECT_MESSAGE_EMAIL_VERIFICATION_REQUIRED: 'DIRECT_MESSAGE_EMAIL_VERIFICATION_REQUIRED',
|
||||
ENFORCEMENT_ACCOUNT_CLOSED: 'ENFORCEMENT_ACCOUNT_CLOSED',
|
||||
ENFORCEMENT_DECISION_ENDED: 'ENFORCEMENT_DECISION_ENDED',
|
||||
ENFORCEMENT_DECISION_OWNS_STATE: 'ENFORCEMENT_DECISION_OWNS_STATE',
|
||||
ENFORCEMENT_DECISION_REPLACED: 'ENFORCEMENT_DECISION_REPLACED',
|
||||
ENFORCEMENT_DELETION_STARTED: 'ENFORCEMENT_DELETION_STARTED',
|
||||
FOLLOW_TARGET_CONTENT_WARNING_REQUIRED: 'FOLLOW_TARGET_CONTENT_WARNING_REQUIRED',
|
||||
FOLLOW_TARGET_NOT_AGE_RESTRICTED: 'FOLLOW_TARGET_NOT_AGE_RESTRICTED',
|
||||
FRIEND_REQUEST_EMAIL_VERIFICATION_REQUIRED: 'FRIEND_REQUEST_EMAIL_VERIFICATION_REQUIRED',
|
||||
@@ -144,6 +149,7 @@ export const APIErrorCodes = {
|
||||
GLOBAL_IP_BANNED: 'GLOBAL_IP_BANNED',
|
||||
GLOBAL_IP_TEMPORARILY_BANNED: 'GLOBAL_IP_TEMPORARILY_BANNED',
|
||||
IP_BAN_DECLINED: 'IP_BAN_DECLINED',
|
||||
IP_BAN_LINKED_TO_DECISION: 'IP_BAN_LINKED_TO_DECISION',
|
||||
MAX_APPLICATIONS: 'MAX_APPLICATIONS',
|
||||
MAX_BOOKMARKS: 'MAX_BOOKMARKS',
|
||||
MAX_ACTIVE_THREADS: 'MAX_ACTIVE_THREADS',
|
||||
@@ -258,6 +264,7 @@ export const APIErrorCodes = {
|
||||
UNCLAIMED_ACCOUNT_CANNOT_SUBMIT_REPORTS: 'UNCLAIMED_ACCOUNT_CANNOT_SUBMIT_REPORTS',
|
||||
UNKNOWN_CHANNEL: 'UNKNOWN_CHANNEL',
|
||||
UNKNOWN_EMOJI: 'UNKNOWN_EMOJI',
|
||||
UNKNOWN_ENFORCEMENT_DECISION: 'UNKNOWN_ENFORCEMENT_DECISION',
|
||||
UNKNOWN_FAVORITE_MEME: 'UNKNOWN_FAVORITE_MEME',
|
||||
UNKNOWN_FORUM_TAG: 'UNKNOWN_FORUM_TAG',
|
||||
UNKNOWN_GIFT_CODE: 'UNKNOWN_GIFT_CODE',
|
||||
|
||||
@@ -27,4 +27,13 @@ export const DeletionReasons = {
|
||||
HATE_SPEECH_OR_EXTREMIST_CONTENT: 20,
|
||||
MALICIOUS_LINKS_OR_MALWARE: 21,
|
||||
IMPERSONATION_OR_FAKE_IDENTITY: 22,
|
||||
THREATS_OR_VIOLENT_SPEECH: 23,
|
||||
VIOLENT_EXTREMISM: 24,
|
||||
SELF_HARM_PROMOTION: 25,
|
||||
SEXUAL_CONTENT_PLACEMENT: 26,
|
||||
ILLEGAL_GOODS_OR_ACTIVITY: 27,
|
||||
HARMFUL_DECEPTION: 28,
|
||||
REPORTING_ABUSE: 29,
|
||||
UNDERAGE_ACCOUNT: 30,
|
||||
COPYRIGHT: 31,
|
||||
} as const;
|
||||
@@ -0,0 +1,170 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {DeletionReasons} from '@fluxer/constants/src/Core';
|
||||
import {
|
||||
ACCOUNT_DELETION_MIN_DAYS,
|
||||
ENFORCEMENT_APPEAL_WINDOW_DAYS,
|
||||
ENFORCEMENT_CLOSURE_MIN_DAYS,
|
||||
ENFORCEMENT_REASONS,
|
||||
ENFORCEMENT_RULES,
|
||||
type EnforcementKind,
|
||||
type EnforcementLimit,
|
||||
type EnforcementReasonCode,
|
||||
isEnforcementKindAllowed,
|
||||
isEnforcementLimitAllowed,
|
||||
} from '@fluxer/constants/src/EnforcementReasons';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
const ALL_KINDS: ReadonlyArray<EnforcementKind> = [
|
||||
'warning',
|
||||
'suspension',
|
||||
'closure',
|
||||
'feature_limit',
|
||||
'content_removal',
|
||||
'profile_reset',
|
||||
'community_removal',
|
||||
];
|
||||
|
||||
const ALL_LIMITS: ReadonlyArray<EnforcementLimit> = ['direct_messages', 'reports', 'purchases'];
|
||||
|
||||
const RULE_TABLE = [
|
||||
['spam', 'guidelines', '6-spam-and-service-abuse', true, true],
|
||||
['service_abuse', 'guidelines', '6-spam-and-service-abuse', true, true],
|
||||
['raids', 'guidelines', '1-harassment-threats-and-privacy', true, true],
|
||||
['automation', 'guidelines', '6-spam-and-service-abuse', true, true],
|
||||
['intimate_images', 'guidelines', '1-harassment-threats-and-privacy', true, false],
|
||||
['fraud', 'guidelines', '5-illegal-and-dangerous-activity', true, false],
|
||||
['child_safety', 'guidelines', '4-sexual-content-and-protection-of-minors', true, false],
|
||||
['privacy', 'guidelines', '1-harassment-threats-and-privacy', true, false],
|
||||
['harassment', 'guidelines', '1-harassment-threats-and-privacy', true, false],
|
||||
['payments', 'terms', '4-4-failed-payments-and-disputes', true, false],
|
||||
['unwanted_sexual_content', 'guidelines', '1-harassment-threats-and-privacy', true, false],
|
||||
['violent_content', 'guidelines', '3-violence-extremism-and-self-harm', true, true],
|
||||
['ban_evasion', 'guidelines', '6-spam-and-service-abuse', true, false],
|
||||
['hate', 'guidelines', '2-hate-and-discrimination', true, false],
|
||||
['malware', 'guidelines', '5-illegal-and-dangerous-activity', true, false],
|
||||
['impersonation', 'guidelines', '6-spam-and-service-abuse', true, true],
|
||||
['threats', 'guidelines', '1-harassment-threats-and-privacy', true, false],
|
||||
['violent_extremism', 'guidelines', '3-violence-extremism-and-self-harm', true, false],
|
||||
['self_harm', 'guidelines', '3-violence-extremism-and-self-harm', true, false],
|
||||
['adult_content', 'guidelines', '4-sexual-content-and-protection-of-minors', true, true],
|
||||
['illegal_activity', 'guidelines', '5-illegal-and-dangerous-activity', true, false],
|
||||
['harmful_deception', 'guidelines', '7-harmful-deception-and-manipulated-media', true, false],
|
||||
['report_abuse', 'guidelines', '8-reporting', true, true],
|
||||
['age_requirement', 'terms', '1-agreement-and-eligibility', false, false],
|
||||
['copyright', 'terms', '3-4-copyright', true, true],
|
||||
] as const;
|
||||
|
||||
const CODE_TABLE = [
|
||||
[3, 'spam', 'standard', 'any'],
|
||||
[4, 'service_abuse', 'standard', 'any'],
|
||||
[5, 'raids', 'standard', 'any'],
|
||||
[6, 'automation', 'standard', 'any'],
|
||||
[7, 'intimate_images', 'protected', 'any'],
|
||||
[8, 'fraud', 'standard', 'any'],
|
||||
[9, 'child_safety', 'restricted', ['suspension', 'content_removal']],
|
||||
[10, 'privacy', 'protected', 'any'],
|
||||
[11, 'harassment', 'protected', 'any'],
|
||||
[12, 'payments', 'standard', ['warning', 'suspension', 'closure', 'feature_limit']],
|
||||
[13, 'child_safety', 'restricted', ['suspension', 'content_removal']],
|
||||
[14, 'payments', 'standard', ['warning', 'suspension', 'closure', 'feature_limit']],
|
||||
[15, 'unwanted_sexual_content', 'protected', 'any'],
|
||||
[16, 'violent_content', 'standard', 'any'],
|
||||
[17, 'ban_evasion', 'standard', ['warning', 'suspension', 'closure']],
|
||||
[18, 'fraud', 'standard', 'any'],
|
||||
[20, 'hate', 'standard', 'any'],
|
||||
[21, 'malware', 'standard', 'any'],
|
||||
[22, 'impersonation', 'standard', 'any'],
|
||||
[23, 'threats', 'protected', 'any'],
|
||||
[24, 'violent_extremism', 'restricted', ['suspension', 'content_removal']],
|
||||
[25, 'self_harm', 'standard', ['warning', 'suspension', 'content_removal', 'profile_reset', 'community_removal']],
|
||||
[26, 'adult_content', 'standard', 'any'],
|
||||
[27, 'illegal_activity', 'standard', 'any'],
|
||||
[28, 'harmful_deception', 'standard', 'any'],
|
||||
[29, 'report_abuse', 'standard', ['warning', 'feature_limit', 'suspension']],
|
||||
[30, 'age_requirement', 'standard', ['closure']],
|
||||
[31, 'copyright', 'standard', ['warning', 'content_removal', 'profile_reset', 'community_removal']],
|
||||
] as const;
|
||||
|
||||
const LIMIT_TABLE: ReadonlyArray<readonly [EnforcementLimit, ReadonlyArray<number>]> = [
|
||||
['direct_messages', [3, 6, 8, 18, 21]],
|
||||
['reports', [29]],
|
||||
['purchases', [12, 14]],
|
||||
];
|
||||
|
||||
const enforcementCodes = Object.keys(ENFORCEMENT_REASONS).map(Number) as Array<EnforcementReasonCode>;
|
||||
|
||||
describe('ENFORCEMENT_REASONS', () => {
|
||||
it('classifies every deletion reason except the three non-enforcement codes', () => {
|
||||
const expected = Object.values(DeletionReasons).filter(
|
||||
(code) =>
|
||||
code !== DeletionReasons.USER_REQUESTED &&
|
||||
code !== DeletionReasons.OTHER &&
|
||||
code !== DeletionReasons.INACTIVITY,
|
||||
);
|
||||
expect(enforcementCodes.sort((a, b) => a - b)).toEqual(expected.sort((a, b) => a - b));
|
||||
});
|
||||
|
||||
it('matches the code table', () => {
|
||||
expect(
|
||||
Object.entries(ENFORCEMENT_REASONS).map(([code, reason]) => [
|
||||
Number(code),
|
||||
reason.rule,
|
||||
reason.handling,
|
||||
reason.kinds,
|
||||
]),
|
||||
).toEqual(CODE_TABLE);
|
||||
});
|
||||
|
||||
it('only names rules that exist', () => {
|
||||
for (const reason of Object.values(ENFORCEMENT_REASONS)) {
|
||||
expect(ENFORCEMENT_RULES).toHaveProperty(reason.rule);
|
||||
}
|
||||
});
|
||||
|
||||
it('limits restricted codes to permanent suspension and content removal', () => {
|
||||
for (const code of enforcementCodes) {
|
||||
if (ENFORCEMENT_REASONS[code].handling !== 'restricted') continue;
|
||||
expect(ALL_KINDS.filter((kind) => isEnforcementKindAllowed(code, kind))).toEqual([
|
||||
'suspension',
|
||||
'content_removal',
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ENFORCEMENT_RULES', () => {
|
||||
it('pins policy anchors, the new account bar and network screen visibility', () => {
|
||||
expect(
|
||||
Object.entries(ENFORCEMENT_RULES).map(([key, rule]) => [
|
||||
key,
|
||||
rule.policy,
|
||||
rule.anchor,
|
||||
rule.barsNewAccounts,
|
||||
rule.showOnNetworkScreen,
|
||||
]),
|
||||
).toEqual(RULE_TABLE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('enforcement limits', () => {
|
||||
it.each(LIMIT_TABLE)('allows %s only for its codes', (limit, codes) => {
|
||||
expect(enforcementCodes.filter((code) => isEnforcementLimitAllowed(code, limit))).toEqual(codes);
|
||||
});
|
||||
|
||||
it('allows a feature limit only where some limit applies', () => {
|
||||
const withFeatureLimit = enforcementCodes.filter((code) => isEnforcementKindAllowed(code, 'feature_limit'));
|
||||
const withAnyLimit = enforcementCodes.filter((code) =>
|
||||
ALL_LIMITS.some((limit) => isEnforcementLimitAllowed(code, limit)),
|
||||
);
|
||||
expect(withFeatureLimit).toEqual(withAnyLimit);
|
||||
});
|
||||
});
|
||||
|
||||
describe('enforcement windows', () => {
|
||||
it('keeps closures open past the appeal window', () => {
|
||||
expect(ACCOUNT_DELETION_MIN_DAYS).toBe(14);
|
||||
expect(ENFORCEMENT_APPEAL_WINDOW_DAYS).toBe(60);
|
||||
expect(ENFORCEMENT_CLOSURE_MIN_DAYS).toBe(ENFORCEMENT_APPEAL_WINDOW_DAYS + ACCOUNT_DELETION_MIN_DAYS);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,207 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {DeletionReasons} from '@fluxer/constants/src/Core';
|
||||
import type {ValueOf} from '@fluxer/constants/src/ValueOf';
|
||||
|
||||
export type EnforcementRuleKey =
|
||||
| 'spam'
|
||||
| 'service_abuse'
|
||||
| 'raids'
|
||||
| 'automation'
|
||||
| 'intimate_images'
|
||||
| 'fraud'
|
||||
| 'child_safety'
|
||||
| 'privacy'
|
||||
| 'harassment'
|
||||
| 'payments'
|
||||
| 'unwanted_sexual_content'
|
||||
| 'violent_content'
|
||||
| 'ban_evasion'
|
||||
| 'hate'
|
||||
| 'malware'
|
||||
| 'impersonation'
|
||||
| 'threats'
|
||||
| 'violent_extremism'
|
||||
| 'self_harm'
|
||||
| 'adult_content'
|
||||
| 'illegal_activity'
|
||||
| 'harmful_deception'
|
||||
| 'report_abuse'
|
||||
| 'age_requirement'
|
||||
| 'copyright';
|
||||
|
||||
type EnforcementHandling = 'standard' | 'protected' | 'restricted';
|
||||
|
||||
export type EnforcementKind =
|
||||
| 'warning'
|
||||
| 'suspension'
|
||||
| 'closure'
|
||||
| 'feature_limit'
|
||||
| 'content_removal'
|
||||
| 'profile_reset'
|
||||
| 'community_removal';
|
||||
|
||||
export type EnforcementLimit = 'direct_messages' | 'reports' | 'purchases';
|
||||
|
||||
export type EnforcementReasonCode = Exclude<
|
||||
ValueOf<typeof DeletionReasons>,
|
||||
typeof DeletionReasons.USER_REQUESTED | typeof DeletionReasons.OTHER | typeof DeletionReasons.INACTIVITY
|
||||
>;
|
||||
|
||||
interface EnforcementRule {
|
||||
policy: 'guidelines' | 'terms';
|
||||
anchor: string;
|
||||
barsNewAccounts: boolean;
|
||||
showOnNetworkScreen: boolean;
|
||||
}
|
||||
|
||||
interface EnforcementReason {
|
||||
rule: EnforcementRuleKey;
|
||||
handling: EnforcementHandling;
|
||||
kinds: ReadonlyArray<EnforcementKind> | 'any';
|
||||
}
|
||||
|
||||
export const ACCOUNT_DELETION_MIN_DAYS = 14;
|
||||
export const ENFORCEMENT_APPEAL_WINDOW_DAYS = 60;
|
||||
export const ENFORCEMENT_CLOSURE_MIN_DAYS = ENFORCEMENT_APPEAL_WINDOW_DAYS + ACCOUNT_DELETION_MIN_DAYS;
|
||||
|
||||
const HARASSMENT_ANCHOR = '1-harassment-threats-and-privacy';
|
||||
const VIOLENCE_ANCHOR = '3-violence-extremism-and-self-harm';
|
||||
const SEXUAL_CONTENT_ANCHOR = '4-sexual-content-and-protection-of-minors';
|
||||
const ILLEGAL_ACTIVITY_ANCHOR = '5-illegal-and-dangerous-activity';
|
||||
const SERVICE_ABUSE_ANCHOR = '6-spam-and-service-abuse';
|
||||
|
||||
function guidelinesRule(anchor: string, showOnNetworkScreen: boolean): EnforcementRule {
|
||||
return {policy: 'guidelines', anchor, barsNewAccounts: true, showOnNetworkScreen};
|
||||
}
|
||||
|
||||
export const ENFORCEMENT_RULES: Readonly<Record<EnforcementRuleKey, EnforcementRule>> = {
|
||||
spam: guidelinesRule(SERVICE_ABUSE_ANCHOR, true),
|
||||
service_abuse: guidelinesRule(SERVICE_ABUSE_ANCHOR, true),
|
||||
raids: guidelinesRule(HARASSMENT_ANCHOR, true),
|
||||
automation: guidelinesRule(SERVICE_ABUSE_ANCHOR, true),
|
||||
intimate_images: guidelinesRule(HARASSMENT_ANCHOR, false),
|
||||
fraud: guidelinesRule(ILLEGAL_ACTIVITY_ANCHOR, false),
|
||||
child_safety: guidelinesRule(SEXUAL_CONTENT_ANCHOR, false),
|
||||
privacy: guidelinesRule(HARASSMENT_ANCHOR, false),
|
||||
harassment: guidelinesRule(HARASSMENT_ANCHOR, false),
|
||||
payments: {
|
||||
policy: 'terms',
|
||||
anchor: '4-4-failed-payments-and-disputes',
|
||||
barsNewAccounts: true,
|
||||
showOnNetworkScreen: false,
|
||||
},
|
||||
unwanted_sexual_content: guidelinesRule(HARASSMENT_ANCHOR, false),
|
||||
violent_content: guidelinesRule(VIOLENCE_ANCHOR, true),
|
||||
ban_evasion: guidelinesRule(SERVICE_ABUSE_ANCHOR, false),
|
||||
hate: guidelinesRule('2-hate-and-discrimination', false),
|
||||
malware: guidelinesRule(ILLEGAL_ACTIVITY_ANCHOR, false),
|
||||
impersonation: guidelinesRule(SERVICE_ABUSE_ANCHOR, true),
|
||||
threats: guidelinesRule(HARASSMENT_ANCHOR, false),
|
||||
violent_extremism: guidelinesRule(VIOLENCE_ANCHOR, false),
|
||||
self_harm: guidelinesRule(VIOLENCE_ANCHOR, false),
|
||||
adult_content: guidelinesRule(SEXUAL_CONTENT_ANCHOR, true),
|
||||
illegal_activity: guidelinesRule(ILLEGAL_ACTIVITY_ANCHOR, false),
|
||||
harmful_deception: guidelinesRule('7-harmful-deception-and-manipulated-media', false),
|
||||
report_abuse: guidelinesRule('8-reporting', true),
|
||||
age_requirement: {
|
||||
policy: 'terms',
|
||||
anchor: '1-agreement-and-eligibility',
|
||||
barsNewAccounts: false,
|
||||
showOnNetworkScreen: false,
|
||||
},
|
||||
copyright: {policy: 'terms', anchor: '3-4-copyright', barsNewAccounts: true, showOnNetworkScreen: true},
|
||||
};
|
||||
|
||||
const RESTRICTED_KINDS: ReadonlyArray<EnforcementKind> = ['suspension', 'content_removal'];
|
||||
const PAYMENT_KINDS: ReadonlyArray<EnforcementKind> = ['warning', 'suspension', 'closure', 'feature_limit'];
|
||||
|
||||
export const ENFORCEMENT_REASONS: Readonly<Record<EnforcementReasonCode, EnforcementReason>> = {
|
||||
[DeletionReasons.SPAM]: {rule: 'spam', handling: 'standard', kinds: 'any'},
|
||||
[DeletionReasons.CHEATING_OR_EXPLOITATION]: {rule: 'service_abuse', handling: 'standard', kinds: 'any'},
|
||||
[DeletionReasons.COORDINATED_RAIDING]: {rule: 'raids', handling: 'standard', kinds: 'any'},
|
||||
[DeletionReasons.AUTOMATION_OR_SELFBOT]: {rule: 'automation', handling: 'standard', kinds: 'any'},
|
||||
[DeletionReasons.NONCONSENSUAL_SEXUAL_CONTENT]: {rule: 'intimate_images', handling: 'protected', kinds: 'any'},
|
||||
[DeletionReasons.SCAM_OR_SOCIAL_ENGINEERING]: {rule: 'fraud', handling: 'standard', kinds: 'any'},
|
||||
[DeletionReasons.CHILD_SEXUAL_CONTENT]: {rule: 'child_safety', handling: 'restricted', kinds: RESTRICTED_KINDS},
|
||||
[DeletionReasons.PRIVACY_VIOLATION_OR_DOXXING]: {rule: 'privacy', handling: 'protected', kinds: 'any'},
|
||||
[DeletionReasons.HARASSMENT_OR_BULLYING]: {rule: 'harassment', handling: 'protected', kinds: 'any'},
|
||||
[DeletionReasons.PAYMENT_FRAUD]: {rule: 'payments', handling: 'standard', kinds: PAYMENT_KINDS},
|
||||
[DeletionReasons.CHILD_SAFETY_VIOLATION]: {rule: 'child_safety', handling: 'restricted', kinds: RESTRICTED_KINDS},
|
||||
[DeletionReasons.BILLING_DISPUTE_OR_ABUSE]: {rule: 'payments', handling: 'standard', kinds: PAYMENT_KINDS},
|
||||
[DeletionReasons.UNSOLICITED_EXPLICIT_CONTENT]: {
|
||||
rule: 'unwanted_sexual_content',
|
||||
handling: 'protected',
|
||||
kinds: 'any',
|
||||
},
|
||||
[DeletionReasons.GRAPHIC_VIOLENCE]: {rule: 'violent_content', handling: 'standard', kinds: 'any'},
|
||||
[DeletionReasons.BAN_EVASION]: {
|
||||
rule: 'ban_evasion',
|
||||
handling: 'standard',
|
||||
kinds: ['warning', 'suspension', 'closure'],
|
||||
},
|
||||
[DeletionReasons.TOKEN_OR_CREDENTIAL_SCAM]: {rule: 'fraud', handling: 'standard', kinds: 'any'},
|
||||
[DeletionReasons.HATE_SPEECH_OR_EXTREMIST_CONTENT]: {rule: 'hate', handling: 'standard', kinds: 'any'},
|
||||
[DeletionReasons.MALICIOUS_LINKS_OR_MALWARE]: {rule: 'malware', handling: 'standard', kinds: 'any'},
|
||||
[DeletionReasons.IMPERSONATION_OR_FAKE_IDENTITY]: {rule: 'impersonation', handling: 'standard', kinds: 'any'},
|
||||
[DeletionReasons.THREATS_OR_VIOLENT_SPEECH]: {rule: 'threats', handling: 'protected', kinds: 'any'},
|
||||
[DeletionReasons.VIOLENT_EXTREMISM]: {rule: 'violent_extremism', handling: 'restricted', kinds: RESTRICTED_KINDS},
|
||||
[DeletionReasons.SELF_HARM_PROMOTION]: {
|
||||
rule: 'self_harm',
|
||||
handling: 'standard',
|
||||
kinds: ['warning', 'suspension', 'content_removal', 'profile_reset', 'community_removal'],
|
||||
},
|
||||
[DeletionReasons.SEXUAL_CONTENT_PLACEMENT]: {rule: 'adult_content', handling: 'standard', kinds: 'any'},
|
||||
[DeletionReasons.ILLEGAL_GOODS_OR_ACTIVITY]: {rule: 'illegal_activity', handling: 'standard', kinds: 'any'},
|
||||
[DeletionReasons.HARMFUL_DECEPTION]: {rule: 'harmful_deception', handling: 'standard', kinds: 'any'},
|
||||
[DeletionReasons.REPORTING_ABUSE]: {
|
||||
rule: 'report_abuse',
|
||||
handling: 'standard',
|
||||
kinds: ['warning', 'feature_limit', 'suspension'],
|
||||
},
|
||||
[DeletionReasons.UNDERAGE_ACCOUNT]: {rule: 'age_requirement', handling: 'standard', kinds: ['closure']},
|
||||
[DeletionReasons.COPYRIGHT]: {
|
||||
rule: 'copyright',
|
||||
handling: 'standard',
|
||||
kinds: ['warning', 'content_removal', 'profile_reset', 'community_removal'],
|
||||
},
|
||||
};
|
||||
|
||||
const ENFORCEMENT_SPAMMER_LIMIT_RULES: ReadonlySet<EnforcementRuleKey> = new Set([
|
||||
'spam',
|
||||
'automation',
|
||||
'fraud',
|
||||
'malware',
|
||||
]);
|
||||
|
||||
const ENFORCEMENT_LIMIT_RULES: Readonly<Record<EnforcementLimit, ReadonlySet<EnforcementRuleKey>>> = {
|
||||
direct_messages: ENFORCEMENT_SPAMMER_LIMIT_RULES,
|
||||
reports: new Set(['report_abuse']),
|
||||
purchases: new Set(['payments']),
|
||||
};
|
||||
|
||||
const ACCOUNT_KINDS: ReadonlySet<EnforcementKind> = new Set(['suspension', 'closure']);
|
||||
|
||||
export function isAccountKind(kind: EnforcementKind): boolean {
|
||||
return ACCOUNT_KINDS.has(kind);
|
||||
}
|
||||
|
||||
export function isEnforcementReasonCode(code: number | null | undefined): code is EnforcementReasonCode {
|
||||
return code !== null && code !== undefined && Object.hasOwn(ENFORCEMENT_REASONS, code);
|
||||
}
|
||||
|
||||
export function enforcementReasonOf(code: number | null | undefined): EnforcementReason | null {
|
||||
return isEnforcementReasonCode(code) ? ENFORCEMENT_REASONS[code] : null;
|
||||
}
|
||||
|
||||
const ENFORCEMENT_LIMITS = Object.keys(ENFORCEMENT_LIMIT_RULES) as ReadonlyArray<EnforcementLimit>;
|
||||
|
||||
export function isEnforcementLimitAllowed(code: EnforcementReasonCode, limit: EnforcementLimit): boolean {
|
||||
return ENFORCEMENT_LIMIT_RULES[limit].has(ENFORCEMENT_REASONS[code].rule);
|
||||
}
|
||||
|
||||
export function isEnforcementKindAllowed(code: EnforcementReasonCode, kind: EnforcementKind): boolean {
|
||||
const {kinds} = ENFORCEMENT_REASONS[code];
|
||||
if (kinds !== 'any' && !kinds.includes(kind)) return false;
|
||||
return kind !== 'feature_limit' || ENFORCEMENT_LIMITS.some((limit) => isEnforcementLimitAllowed(code, limit));
|
||||
}
|
||||
@@ -10,7 +10,6 @@ export const ValidationErrorCodes = {
|
||||
ALL_CHANNELS_MUST_BELONG_TO_GUILD: 'ALL_CHANNELS_MUST_BELONG_TO_GUILD',
|
||||
ANIMATED_AVATARS_REQUIRE_PREMIUM: 'ANIMATED_AVATARS_REQUIRE_PREMIUM',
|
||||
ANIMATED_GUILD_BANNER_REQUIRES_FEATURE: 'ANIMATED_GUILD_BANNER_REQUIRES_FEATURE',
|
||||
AT_LEAST_ONE_ENTRY_IS_REQUIRED: 'AT_LEAST_ONE_ENTRY_IS_REQUIRED',
|
||||
AT_LEAST_ONE_RECIPIENT_REQUIRED: 'AT_LEAST_ONE_RECIPIENT_REQUIRED',
|
||||
ATTACHMENT_ID_NOT_FOUND_IN_MESSAGE: 'ATTACHMENT_ID_NOT_FOUND_IN_MESSAGE',
|
||||
ATTACHMENT_MUST_BE_IMAGE: 'ATTACHMENT_MUST_BE_IMAGE',
|
||||
@@ -72,6 +71,27 @@ export const ValidationErrorCodes = {
|
||||
EMAIL_TOKEN_EXPIRED: 'EMAIL_TOKEN_EXPIRED',
|
||||
EMAIL_DOMAIN_CANNOT_RECEIVE_MAIL: 'EMAIL_DOMAIN_CANNOT_RECEIVE_MAIL',
|
||||
EMAIL_DOMAIN_NOT_ALLOWED_FOR_SSO: 'EMAIL_DOMAIN_NOT_ALLOWED_FOR_SSO',
|
||||
ENFORCEMENT_CONFIRMATIONS_REQUIRED: 'ENFORCEMENT_CONFIRMATIONS_REQUIRED',
|
||||
ENFORCEMENT_EVIDENCE_DM_UNREPORTED: 'ENFORCEMENT_EVIDENCE_DM_UNREPORTED',
|
||||
ENFORCEMENT_EVIDENCE_NOT_ALLOWED: 'ENFORCEMENT_EVIDENCE_NOT_ALLOWED',
|
||||
ENFORCEMENT_EVIDENCE_NOT_FOUND: 'ENFORCEMENT_EVIDENCE_NOT_FOUND',
|
||||
ENFORCEMENT_EVIDENCE_NOT_SUBJECT: 'ENFORCEMENT_EVIDENCE_NOT_SUBJECT',
|
||||
ENFORCEMENT_EVIDENCE_REQUIRED: 'ENFORCEMENT_EVIDENCE_REQUIRED',
|
||||
ENFORCEMENT_FACTS_REQUIRED: 'ENFORCEMENT_FACTS_REQUIRED',
|
||||
ENFORCEMENT_INTERNAL_NOTE_REQUIRED: 'ENFORCEMENT_INTERNAL_NOTE_REQUIRED',
|
||||
ENFORCEMENT_KIND_NOT_ALLOWED: 'ENFORCEMENT_KIND_NOT_ALLOWED',
|
||||
ENFORCEMENT_LIMIT_END_INVALID: 'ENFORCEMENT_LIMIT_END_INVALID',
|
||||
ENFORCEMENT_LIMIT_NOT_ALLOWED: 'ENFORCEMENT_LIMIT_NOT_ALLOWED',
|
||||
ENFORCEMENT_NETWORK_BLOCK_NOT_ALLOWED: 'ENFORCEMENT_NETWORK_BLOCK_NOT_ALLOWED',
|
||||
ENFORCEMENT_NETWORK_SHARED: 'ENFORCEMENT_NETWORK_SHARED',
|
||||
ENFORCEMENT_NETWORK_UNKNOWN: 'ENFORCEMENT_NETWORK_UNKNOWN',
|
||||
ENFORCEMENT_OUTCOME_NOT_ALLOWED: 'ENFORCEMENT_OUTCOME_NOT_ALLOWED',
|
||||
ENFORCEMENT_PERMANENT_REQUIRED: 'ENFORCEMENT_PERMANENT_REQUIRED',
|
||||
ENFORCEMENT_REPORT_MISMATCH: 'ENFORCEMENT_REPORT_MISMATCH',
|
||||
ENFORCEMENT_REPORT_REQUIRED: 'ENFORCEMENT_REPORT_REQUIRED',
|
||||
ENFORCEMENT_RIGHTS_HOLDER_NOT_ALLOWED: 'ENFORCEMENT_RIGHTS_HOLDER_NOT_ALLOWED',
|
||||
ENFORCEMENT_RIGHTS_HOLDER_REQUIRED: 'ENFORCEMENT_RIGHTS_HOLDER_REQUIRED',
|
||||
ENFORCEMENT_STATEMENT_NOT_ALLOWED: 'ENFORCEMENT_STATEMENT_NOT_ALLOWED',
|
||||
EMBED_INDEX_OUT_OF_BOUNDS: 'EMBED_INDEX_OUT_OF_BOUNDS',
|
||||
EMBED_SPLASH_REQUIRES_FEATURE: 'EMBED_SPLASH_REQUIRES_FEATURE',
|
||||
EMBEDS_EXCEED_MAX_CHARACTERS: 'EMBEDS_EXCEED_MAX_CHARACTERS',
|
||||
@@ -83,6 +103,7 @@ export const ValidationErrorCodes = {
|
||||
FAVORITE_MEME_NOT_FOUND: 'FAVORITE_MEME_NOT_FOUND',
|
||||
FILE_INDEX_EXCEEDS_MAXIMUM: 'FILE_INDEX_EXCEEDS_MAXIMUM',
|
||||
FILE_NOT_FOUND: 'FILE_NOT_FOUND',
|
||||
FLAG_REQUIRES_ENFORCEMENT_ROUTE: 'FLAG_REQUIRES_ENFORCEMENT_ROUTE',
|
||||
FILENAME_LENGTH_INVALID: 'FILENAME_LENGTH_INVALID',
|
||||
FORWARD_MESSAGES_CANNOT_CONTAIN_CONTENT: 'FORWARD_MESSAGES_CANNOT_CONTAIN_CONTENT',
|
||||
FORWARD_REFERENCE_REQUIRES_CHANNEL_AND_MESSAGE: 'FORWARD_REFERENCE_REQUIRES_CHANNEL_AND_MESSAGE',
|
||||
@@ -149,6 +170,7 @@ export const ValidationErrorCodes = {
|
||||
MESSAGE_IDS_CANNOT_BE_EMPTY: 'MESSAGE_IDS_CANNOT_BE_EMPTY',
|
||||
MESSAGES_ARRAY_REQUIRED_AND_NOT_EMPTY: 'MESSAGES_ARRAY_REQUIRED_AND_NOT_EMPTY',
|
||||
MESSAGES_WITH_SNAPSHOTS_CANNOT_BE_EDITED: 'MESSAGES_WITH_SNAPSHOTS_CANNOT_BE_EDITED',
|
||||
MODERATION_BASIS_REQUIRED: 'MODERATION_BASIS_REQUIRED',
|
||||
MULTIPLE_FILES_FOR_INDEX_NOT_ALLOWED: 'MULTIPLE_FILES_FOR_INDEX_NOT_ALLOWED',
|
||||
MUST_AGREE_TO_TOS_AND_PRIVACY_POLICY: 'MUST_AGREE_TO_TOS_AND_PRIVACY_POLICY',
|
||||
MUST_BE_MINIMUM_AGE: 'MUST_BE_MINIMUM_AGE',
|
||||
@@ -180,6 +202,7 @@ export const ValidationErrorCodes = {
|
||||
RECIPIENT_IDS_MUST_BE_STRINGS: 'RECIPIENT_IDS_MUST_BE_STRINGS',
|
||||
RECIPIENT_IDS_MUST_BE_VALID_SNOWFLAKES: 'RECIPIENT_IDS_MUST_BE_VALID_SNOWFLAKES',
|
||||
REFERENCED_ATTACHMENT_NOT_FOUND: 'REFERENCED_ATTACHMENT_NOT_FOUND',
|
||||
REPORT_OUTCOME_INVALID: 'REPORT_OUTCOME_INVALID',
|
||||
SESSION_TIMEOUT: 'SESSION_TIMEOUT',
|
||||
SETUP_TOKEN_INVALID: 'SETUP_TOKEN_INVALID',
|
||||
SETUP_TOKEN_REQUIRED: 'SETUP_TOKEN_REQUIRED',
|
||||
|
||||
Reference in new issue
Block a user