mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-06 06:46:04 +02:00
refactor utils
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
import { createDefaultAvatarRef } from '../avatarTransforms';
|
||||||
|
|
||||||
|
describe('createDefaultAvatarRef', () => {
|
||||||
|
it('creates object with defaults', () => {
|
||||||
|
const ref = createDefaultAvatarRef({});
|
||||||
|
expect(ref.id).toBe('');
|
||||||
|
expect(ref.name).toBe('');
|
||||||
|
expect(ref.version).toBe(0);
|
||||||
|
expect(ref.tags).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('spreads json over defaults', () => {
|
||||||
|
const ref = createDefaultAvatarRef({
|
||||||
|
id: 'avtr_123',
|
||||||
|
name: 'My Avatar'
|
||||||
|
});
|
||||||
|
expect(ref.id).toBe('avtr_123');
|
||||||
|
expect(ref.name).toBe('My Avatar');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,66 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
sanitizeUserJson,
|
|
||||||
sanitizeEntityJson,
|
sanitizeEntityJson,
|
||||||
computeTrustLevel,
|
|
||||||
computeUserPlatform,
|
|
||||||
computeDisabledContentSettings,
|
|
||||||
diffObjectProps,
|
|
||||||
createDefaultUserRef,
|
|
||||||
createDefaultWorldRef,
|
|
||||||
createDefaultAvatarRef,
|
|
||||||
createDefaultGroupRef,
|
|
||||||
createDefaultInstanceRef,
|
|
||||||
createDefaultFavoriteGroupRef,
|
createDefaultFavoriteGroupRef,
|
||||||
createDefaultFavoriteCachedRef
|
createDefaultFavoriteCachedRef
|
||||||
} from '../entityTransforms';
|
} from '../entityTransforms';
|
||||||
|
|
||||||
describe('sanitizeUserJson', () => {
|
|
||||||
it('applies replaceBioSymbols to statusDescription, bio, note', () => {
|
|
||||||
const json = {
|
|
||||||
statusDescription: 'hello? world',
|
|
||||||
bio: 'test# bio',
|
|
||||||
note: 'test@ note'
|
|
||||||
};
|
|
||||||
sanitizeUserJson(json, '');
|
|
||||||
// replaceBioSymbols replaces Unicode look-alikes with ASCII
|
|
||||||
expect(json.statusDescription).toContain('?');
|
|
||||||
expect(json.bio).toContain('#');
|
|
||||||
expect(json.note).toContain('@');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes emojis from statusDescription', () => {
|
|
||||||
const json = { statusDescription: 'hello 🎉 world' };
|
|
||||||
sanitizeUserJson(json, '');
|
|
||||||
// removeEmojis removes emoji then collapses whitespace
|
|
||||||
expect(json.statusDescription).toBe('hello world');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('strips robot avatar URL', () => {
|
|
||||||
const robotUrl = 'https://example.com/robot.png';
|
|
||||||
const json = {
|
|
||||||
currentAvatarImageUrl: robotUrl,
|
|
||||||
currentAvatarThumbnailImageUrl: 'thumb.png'
|
|
||||||
};
|
|
||||||
sanitizeUserJson(json, robotUrl);
|
|
||||||
expect(json.currentAvatarImageUrl).toBeUndefined();
|
|
||||||
expect(json.currentAvatarThumbnailImageUrl).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('keeps avatar URL when it does not match robot', () => {
|
|
||||||
const json = {
|
|
||||||
currentAvatarImageUrl: 'https://example.com/user.png',
|
|
||||||
currentAvatarThumbnailImageUrl: 'thumb.png'
|
|
||||||
};
|
|
||||||
sanitizeUserJson(json, 'https://example.com/robot.png');
|
|
||||||
expect(json.currentAvatarImageUrl).toBe('https://example.com/user.png');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles missing fields gracefully', () => {
|
|
||||||
const json = { id: 'usr_123' };
|
|
||||||
expect(() => sanitizeUserJson(json, '')).not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('sanitizeEntityJson', () => {
|
describe('sanitizeEntityJson', () => {
|
||||||
it('applies replaceBioSymbols to specified fields', () => {
|
it('applies replaceBioSymbols to specified fields', () => {
|
||||||
const json = {
|
const json = {
|
||||||
@@ -82,286 +25,6 @@ describe('sanitizeEntityJson', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('computeTrustLevel', () => {
|
|
||||||
it('returns Visitor for empty tags', () => {
|
|
||||||
const result = computeTrustLevel([], '');
|
|
||||||
expect(result.trustLevel).toBe('Visitor');
|
|
||||||
expect(result.trustClass).toBe('x-tag-untrusted');
|
|
||||||
expect(result.trustColorKey).toBe('untrusted');
|
|
||||||
expect(result.trustSortNum).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns Trusted User for veteran tags', () => {
|
|
||||||
const result = computeTrustLevel(['system_trust_veteran'], '');
|
|
||||||
expect(result.trustLevel).toBe('Trusted User');
|
|
||||||
expect(result.trustClass).toBe('x-tag-veteran');
|
|
||||||
expect(result.trustColorKey).toBe('veteran');
|
|
||||||
expect(result.trustSortNum).toBe(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns Known User for trusted tags', () => {
|
|
||||||
const result = computeTrustLevel(['system_trust_trusted'], '');
|
|
||||||
expect(result.trustLevel).toBe('Known User');
|
|
||||||
expect(result.trustSortNum).toBe(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns User for known tags', () => {
|
|
||||||
const result = computeTrustLevel(['system_trust_known'], '');
|
|
||||||
expect(result.trustLevel).toBe('User');
|
|
||||||
expect(result.trustSortNum).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns New User for basic tags', () => {
|
|
||||||
const result = computeTrustLevel(['system_trust_basic'], '');
|
|
||||||
expect(result.trustLevel).toBe('New User');
|
|
||||||
expect(result.trustSortNum).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('detects troll status', () => {
|
|
||||||
const result = computeTrustLevel(
|
|
||||||
['system_troll', 'system_trust_known'],
|
|
||||||
''
|
|
||||||
);
|
|
||||||
expect(result.isTroll).toBe(true);
|
|
||||||
expect(result.trustColorKey).toBe('troll');
|
|
||||||
expect(result.trustSortNum).toBeCloseTo(3.1); // 3 + 0.1
|
|
||||||
});
|
|
||||||
|
|
||||||
it('detects probable troll when not already troll', () => {
|
|
||||||
const result = computeTrustLevel(
|
|
||||||
['system_probable_troll', 'system_trust_basic'],
|
|
||||||
''
|
|
||||||
);
|
|
||||||
expect(result.isProbableTroll).toBe(true);
|
|
||||||
expect(result.isTroll).toBe(false);
|
|
||||||
expect(result.trustColorKey).toBe('troll');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('probable troll is not set when already troll', () => {
|
|
||||||
const result = computeTrustLevel(
|
|
||||||
['system_troll', 'system_probable_troll'],
|
|
||||||
''
|
|
||||||
);
|
|
||||||
expect(result.isTroll).toBe(true);
|
|
||||||
expect(result.isProbableTroll).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('detects moderator from developerType', () => {
|
|
||||||
const result = computeTrustLevel([], 'internal');
|
|
||||||
expect(result.isModerator).toBe(true);
|
|
||||||
expect(result.trustColorKey).toBe('vip');
|
|
||||||
expect(result.trustSortNum).toBeCloseTo(1.3); // 1 + 0.3
|
|
||||||
});
|
|
||||||
|
|
||||||
it('detects moderator from admin_moderator tag', () => {
|
|
||||||
const result = computeTrustLevel(
|
|
||||||
['admin_moderator', 'system_trust_veteran'],
|
|
||||||
''
|
|
||||||
);
|
|
||||||
expect(result.isModerator).toBe(true);
|
|
||||||
expect(result.trustColorKey).toBe('vip');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not treat "none" developerType as moderator', () => {
|
|
||||||
const result = computeTrustLevel([], 'none');
|
|
||||||
expect(result.isModerator).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('computeUserPlatform', () => {
|
|
||||||
it('returns platform when valid', () => {
|
|
||||||
expect(computeUserPlatform('standalonewindows', 'android')).toBe(
|
|
||||||
'standalonewindows'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('falls back to last_platform when platform is "offline"', () => {
|
|
||||||
expect(computeUserPlatform('offline', 'android')).toBe('android');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('falls back to last_platform when platform is "web"', () => {
|
|
||||||
expect(computeUserPlatform('web', 'ios')).toBe('ios');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('falls back to last_platform when platform is empty', () => {
|
|
||||||
expect(computeUserPlatform('', 'standalonewindows')).toBe(
|
|
||||||
'standalonewindows'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns empty string when both are empty', () => {
|
|
||||||
expect(computeUserPlatform('', '')).toBe('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('computeDisabledContentSettings', () => {
|
|
||||||
const settingsList = ['gore', 'nudity', 'violence'];
|
|
||||||
|
|
||||||
it('returns empty for null contentSettings', () => {
|
|
||||||
expect(computeDisabledContentSettings(null, settingsList)).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns empty for empty object', () => {
|
|
||||||
expect(computeDisabledContentSettings({}, settingsList)).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns disabled settings (false values)', () => {
|
|
||||||
const result = computeDisabledContentSettings(
|
|
||||||
{ gore: false, nudity: true, violence: false },
|
|
||||||
settingsList
|
|
||||||
);
|
|
||||||
expect(result).toEqual(['gore', 'violence']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('skips undefined settings', () => {
|
|
||||||
const result = computeDisabledContentSettings(
|
|
||||||
{ gore: true },
|
|
||||||
settingsList
|
|
||||||
);
|
|
||||||
expect(result).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('diffObjectProps', () => {
|
|
||||||
const arraysMatch = (a, b) =>
|
|
||||||
a.length === b.length && a.every((v, i) => v === b[i]);
|
|
||||||
|
|
||||||
it('detects changed primitive props', () => {
|
|
||||||
const ref = { name: 'old', id: '1' };
|
|
||||||
const json = { name: 'new', id: '1' };
|
|
||||||
const result = diffObjectProps(ref, json, arraysMatch);
|
|
||||||
expect(result.hasPropChanged).toBe(true);
|
|
||||||
expect(result.changedProps.name).toEqual(['new', 'old']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('detects unchanged props', () => {
|
|
||||||
const ref = { name: 'same', id: '1' };
|
|
||||||
const json = { name: 'same', id: '1' };
|
|
||||||
const result = diffObjectProps(ref, json, arraysMatch);
|
|
||||||
expect(result.hasPropChanged).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('detects changed arrays', () => {
|
|
||||||
const ref = { tags: ['a', 'b'] };
|
|
||||||
const json = { tags: ['a', 'c'] };
|
|
||||||
const result = diffObjectProps(ref, json, arraysMatch);
|
|
||||||
expect(result.hasPropChanged).toBe(true);
|
|
||||||
expect(result.changedProps.tags).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('ignores props only in json (not in ref)', () => {
|
|
||||||
const ref = { id: '1' };
|
|
||||||
const json = { id: '1', newProp: 'value' };
|
|
||||||
const result = diffObjectProps(ref, json, arraysMatch);
|
|
||||||
expect(result.hasPropChanged).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('ignores props only in ref (not in json)', () => {
|
|
||||||
const ref = { id: '1', extra: 'value' };
|
|
||||||
const json = { id: '1' };
|
|
||||||
const result = diffObjectProps(ref, json, arraysMatch);
|
|
||||||
expect(result.hasPropChanged).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createDefaultUserRef', () => {
|
|
||||||
it('creates object with defaults', () => {
|
|
||||||
const ref = createDefaultUserRef({});
|
|
||||||
expect(ref.id).toBe('');
|
|
||||||
expect(ref.displayName).toBe('');
|
|
||||||
expect(ref.tags).toEqual([]);
|
|
||||||
expect(ref.$trustLevel).toBe('Visitor');
|
|
||||||
expect(ref.$platform).toBe('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('spreads json over defaults', () => {
|
|
||||||
const ref = createDefaultUserRef({
|
|
||||||
id: 'usr_123',
|
|
||||||
displayName: 'Test'
|
|
||||||
});
|
|
||||||
expect(ref.id).toBe('usr_123');
|
|
||||||
expect(ref.displayName).toBe('Test');
|
|
||||||
expect(ref.bio).toBe('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createDefaultWorldRef', () => {
|
|
||||||
it('creates object with defaults', () => {
|
|
||||||
const ref = createDefaultWorldRef({});
|
|
||||||
expect(ref.id).toBe('');
|
|
||||||
expect(ref.name).toBe('');
|
|
||||||
expect(ref.capacity).toBe(0);
|
|
||||||
expect(ref.$isLabs).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('spreads json over defaults', () => {
|
|
||||||
const ref = createDefaultWorldRef({
|
|
||||||
id: 'wrld_123',
|
|
||||||
name: 'Test World'
|
|
||||||
});
|
|
||||||
expect(ref.id).toBe('wrld_123');
|
|
||||||
expect(ref.name).toBe('Test World');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createDefaultAvatarRef', () => {
|
|
||||||
it('creates object with defaults', () => {
|
|
||||||
const ref = createDefaultAvatarRef({});
|
|
||||||
expect(ref.id).toBe('');
|
|
||||||
expect(ref.name).toBe('');
|
|
||||||
expect(ref.version).toBe(0);
|
|
||||||
expect(ref.tags).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('spreads json over defaults', () => {
|
|
||||||
const ref = createDefaultAvatarRef({
|
|
||||||
id: 'avtr_123',
|
|
||||||
name: 'My Avatar'
|
|
||||||
});
|
|
||||||
expect(ref.id).toBe('avtr_123');
|
|
||||||
expect(ref.name).toBe('My Avatar');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createDefaultGroupRef', () => {
|
|
||||||
it('creates object with defaults including myMember', () => {
|
|
||||||
const ref = createDefaultGroupRef({});
|
|
||||||
expect(ref.id).toBe('');
|
|
||||||
expect(ref.name).toBe('');
|
|
||||||
expect(ref.myMember).toBeDefined();
|
|
||||||
expect(ref.myMember.roleIds).toEqual([]);
|
|
||||||
expect(ref.roles).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('spreads json over defaults', () => {
|
|
||||||
const ref = createDefaultGroupRef({
|
|
||||||
id: 'grp_123',
|
|
||||||
name: 'Test Group'
|
|
||||||
});
|
|
||||||
expect(ref.id).toBe('grp_123');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createDefaultInstanceRef', () => {
|
|
||||||
it('creates object with defaults', () => {
|
|
||||||
const ref = createDefaultInstanceRef({});
|
|
||||||
expect(ref.id).toBe('');
|
|
||||||
expect(ref.capacity).toBe(0);
|
|
||||||
expect(ref.hasCapacityForYou).toBe(true);
|
|
||||||
expect(ref.$fetchedAt).toBe('');
|
|
||||||
expect(ref.$disabledContentSettings).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('spreads json over defaults', () => {
|
|
||||||
const ref = createDefaultInstanceRef({
|
|
||||||
id: 'wrld_123:12345',
|
|
||||||
capacity: 40
|
|
||||||
});
|
|
||||||
expect(ref.id).toBe('wrld_123:12345');
|
|
||||||
expect(ref.capacity).toBe(40);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createDefaultFavoriteGroupRef', () => {
|
describe('createDefaultFavoriteGroupRef', () => {
|
||||||
it('creates object with defaults', () => {
|
it('creates object with defaults', () => {
|
||||||
const ref = createDefaultFavoriteGroupRef({});
|
const ref = createDefaultFavoriteGroupRef({});
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { createDefaultGroupRef } from '../groupTransforms';
|
||||||
|
|
||||||
|
describe('createDefaultGroupRef', () => {
|
||||||
|
it('creates object with defaults including myMember', () => {
|
||||||
|
const ref = createDefaultGroupRef({});
|
||||||
|
expect(ref.id).toBe('');
|
||||||
|
expect(ref.name).toBe('');
|
||||||
|
expect(ref.myMember).toBeDefined();
|
||||||
|
expect(ref.myMember.roleIds).toEqual([]);
|
||||||
|
expect(ref.roles).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('spreads json over defaults', () => {
|
||||||
|
const ref = createDefaultGroupRef({
|
||||||
|
id: 'grp_123',
|
||||||
|
name: 'Test Group'
|
||||||
|
});
|
||||||
|
expect(ref.id).toBe('grp_123');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import {
|
||||||
|
computeDisabledContentSettings,
|
||||||
|
createDefaultInstanceRef
|
||||||
|
} from '../instanceTransforms';
|
||||||
|
|
||||||
|
describe('computeDisabledContentSettings', () => {
|
||||||
|
const settingsList = ['gore', 'nudity', 'violence'];
|
||||||
|
|
||||||
|
it('returns empty for null contentSettings', () => {
|
||||||
|
expect(computeDisabledContentSettings(null, settingsList)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty for empty object', () => {
|
||||||
|
expect(computeDisabledContentSettings({}, settingsList)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns disabled settings (false values)', () => {
|
||||||
|
const result = computeDisabledContentSettings(
|
||||||
|
{ gore: false, nudity: true, violence: false },
|
||||||
|
settingsList
|
||||||
|
);
|
||||||
|
expect(result).toEqual(['gore', 'violence']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips undefined settings', () => {
|
||||||
|
const result = computeDisabledContentSettings(
|
||||||
|
{ gore: true },
|
||||||
|
settingsList
|
||||||
|
);
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDefaultInstanceRef', () => {
|
||||||
|
it('creates object with defaults', () => {
|
||||||
|
const ref = createDefaultInstanceRef({});
|
||||||
|
expect(ref.id).toBe('');
|
||||||
|
expect(ref.capacity).toBe(0);
|
||||||
|
expect(ref.hasCapacityForYou).toBe(true);
|
||||||
|
expect(ref.$fetchedAt).toBe('');
|
||||||
|
expect(ref.$disabledContentSettings).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('spreads json over defaults', () => {
|
||||||
|
const ref = createDefaultInstanceRef({
|
||||||
|
id: 'wrld_123:12345',
|
||||||
|
capacity: 40
|
||||||
|
});
|
||||||
|
expect(ref.id).toBe('wrld_123:12345');
|
||||||
|
expect(ref.capacity).toBe(40);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
import {
|
||||||
|
sanitizeUserJson,
|
||||||
|
computeTrustLevel,
|
||||||
|
computeUserPlatform,
|
||||||
|
diffObjectProps,
|
||||||
|
createDefaultUserRef
|
||||||
|
} from '../userTransforms';
|
||||||
|
|
||||||
|
describe('sanitizeUserJson', () => {
|
||||||
|
it('applies replaceBioSymbols to statusDescription, bio, note', () => {
|
||||||
|
const json = {
|
||||||
|
statusDescription: 'hello? world',
|
||||||
|
bio: 'test# bio',
|
||||||
|
note: 'test@ note'
|
||||||
|
};
|
||||||
|
sanitizeUserJson(json, '');
|
||||||
|
// replaceBioSymbols replaces Unicode look-alikes with ASCII
|
||||||
|
expect(json.statusDescription).toContain('?');
|
||||||
|
expect(json.bio).toContain('#');
|
||||||
|
expect(json.note).toContain('@');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes emojis from statusDescription', () => {
|
||||||
|
const json = { statusDescription: 'hello 🎉 world' };
|
||||||
|
sanitizeUserJson(json, '');
|
||||||
|
// removeEmojis removes emoji then collapses whitespace
|
||||||
|
expect(json.statusDescription).toBe('hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('strips robot avatar URL', () => {
|
||||||
|
const robotUrl = 'https://example.com/robot.png';
|
||||||
|
const json = {
|
||||||
|
currentAvatarImageUrl: robotUrl,
|
||||||
|
currentAvatarThumbnailImageUrl: 'thumb.png'
|
||||||
|
};
|
||||||
|
sanitizeUserJson(json, robotUrl);
|
||||||
|
expect(json.currentAvatarImageUrl).toBeUndefined();
|
||||||
|
expect(json.currentAvatarThumbnailImageUrl).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps avatar URL when it does not match robot', () => {
|
||||||
|
const json = {
|
||||||
|
currentAvatarImageUrl: 'https://example.com/user.png',
|
||||||
|
currentAvatarThumbnailImageUrl: 'thumb.png'
|
||||||
|
};
|
||||||
|
sanitizeUserJson(json, 'https://example.com/robot.png');
|
||||||
|
expect(json.currentAvatarImageUrl).toBe('https://example.com/user.png');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles missing fields gracefully', () => {
|
||||||
|
const json = { id: 'usr_123' };
|
||||||
|
expect(() => sanitizeUserJson(json, '')).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('computeTrustLevel', () => {
|
||||||
|
it('returns Visitor for empty tags', () => {
|
||||||
|
const result = computeTrustLevel([], '');
|
||||||
|
expect(result.trustLevel).toBe('Visitor');
|
||||||
|
expect(result.trustClass).toBe('x-tag-untrusted');
|
||||||
|
expect(result.trustColorKey).toBe('untrusted');
|
||||||
|
expect(result.trustSortNum).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns Trusted User for veteran tags', () => {
|
||||||
|
const result = computeTrustLevel(['system_trust_veteran'], '');
|
||||||
|
expect(result.trustLevel).toBe('Trusted User');
|
||||||
|
expect(result.trustClass).toBe('x-tag-veteran');
|
||||||
|
expect(result.trustColorKey).toBe('veteran');
|
||||||
|
expect(result.trustSortNum).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns Known User for trusted tags', () => {
|
||||||
|
const result = computeTrustLevel(['system_trust_trusted'], '');
|
||||||
|
expect(result.trustLevel).toBe('Known User');
|
||||||
|
expect(result.trustSortNum).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns User for known tags', () => {
|
||||||
|
const result = computeTrustLevel(['system_trust_known'], '');
|
||||||
|
expect(result.trustLevel).toBe('User');
|
||||||
|
expect(result.trustSortNum).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns New User for basic tags', () => {
|
||||||
|
const result = computeTrustLevel(['system_trust_basic'], '');
|
||||||
|
expect(result.trustLevel).toBe('New User');
|
||||||
|
expect(result.trustSortNum).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects troll status', () => {
|
||||||
|
const result = computeTrustLevel(
|
||||||
|
['system_troll', 'system_trust_known'],
|
||||||
|
''
|
||||||
|
);
|
||||||
|
expect(result.isTroll).toBe(true);
|
||||||
|
expect(result.trustColorKey).toBe('troll');
|
||||||
|
expect(result.trustSortNum).toBeCloseTo(3.1); // 3 + 0.1
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects probable troll when not already troll', () => {
|
||||||
|
const result = computeTrustLevel(
|
||||||
|
['system_probable_troll', 'system_trust_basic'],
|
||||||
|
''
|
||||||
|
);
|
||||||
|
expect(result.isProbableTroll).toBe(true);
|
||||||
|
expect(result.isTroll).toBe(false);
|
||||||
|
expect(result.trustColorKey).toBe('troll');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('probable troll is not set when already troll', () => {
|
||||||
|
const result = computeTrustLevel(
|
||||||
|
['system_troll', 'system_probable_troll'],
|
||||||
|
''
|
||||||
|
);
|
||||||
|
expect(result.isTroll).toBe(true);
|
||||||
|
expect(result.isProbableTroll).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects moderator from developerType', () => {
|
||||||
|
const result = computeTrustLevel([], 'internal');
|
||||||
|
expect(result.isModerator).toBe(true);
|
||||||
|
expect(result.trustColorKey).toBe('vip');
|
||||||
|
expect(result.trustSortNum).toBeCloseTo(1.3); // 1 + 0.3
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects moderator from admin_moderator tag', () => {
|
||||||
|
const result = computeTrustLevel(
|
||||||
|
['admin_moderator', 'system_trust_veteran'],
|
||||||
|
''
|
||||||
|
);
|
||||||
|
expect(result.isModerator).toBe(true);
|
||||||
|
expect(result.trustColorKey).toBe('vip');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not treat "none" developerType as moderator', () => {
|
||||||
|
const result = computeTrustLevel([], 'none');
|
||||||
|
expect(result.isModerator).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('computeUserPlatform', () => {
|
||||||
|
it('returns platform when valid', () => {
|
||||||
|
expect(computeUserPlatform('standalonewindows', 'android')).toBe(
|
||||||
|
'standalonewindows'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to last_platform when platform is "offline"', () => {
|
||||||
|
expect(computeUserPlatform('offline', 'android')).toBe('android');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to last_platform when platform is "web"', () => {
|
||||||
|
expect(computeUserPlatform('web', 'ios')).toBe('ios');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to last_platform when platform is empty', () => {
|
||||||
|
expect(computeUserPlatform('', 'standalonewindows')).toBe(
|
||||||
|
'standalonewindows'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty string when both are empty', () => {
|
||||||
|
expect(computeUserPlatform('', '')).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('diffObjectProps', () => {
|
||||||
|
const arraysMatch = (a, b) =>
|
||||||
|
a.length === b.length && a.every((v, i) => v === b[i]);
|
||||||
|
|
||||||
|
it('detects changed primitive props', () => {
|
||||||
|
const ref = { name: 'old', id: '1' };
|
||||||
|
const json = { name: 'new', id: '1' };
|
||||||
|
const result = diffObjectProps(ref, json, arraysMatch);
|
||||||
|
expect(result.hasPropChanged).toBe(true);
|
||||||
|
expect(result.changedProps.name).toEqual(['new', 'old']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects unchanged props', () => {
|
||||||
|
const ref = { name: 'same', id: '1' };
|
||||||
|
const json = { name: 'same', id: '1' };
|
||||||
|
const result = diffObjectProps(ref, json, arraysMatch);
|
||||||
|
expect(result.hasPropChanged).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects changed arrays', () => {
|
||||||
|
const ref = { tags: ['a', 'b'] };
|
||||||
|
const json = { tags: ['a', 'c'] };
|
||||||
|
const result = diffObjectProps(ref, json, arraysMatch);
|
||||||
|
expect(result.hasPropChanged).toBe(true);
|
||||||
|
expect(result.changedProps.tags).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores props only in json (not in ref)', () => {
|
||||||
|
const ref = { id: '1' };
|
||||||
|
const json = { id: '1', newProp: 'value' };
|
||||||
|
const result = diffObjectProps(ref, json, arraysMatch);
|
||||||
|
expect(result.hasPropChanged).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores props only in ref (not in json)', () => {
|
||||||
|
const ref = { id: '1', extra: 'value' };
|
||||||
|
const json = { id: '1' };
|
||||||
|
const result = diffObjectProps(ref, json, arraysMatch);
|
||||||
|
expect(result.hasPropChanged).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDefaultUserRef', () => {
|
||||||
|
it('creates object with defaults', () => {
|
||||||
|
const ref = createDefaultUserRef({});
|
||||||
|
expect(ref.id).toBe('');
|
||||||
|
expect(ref.displayName).toBe('');
|
||||||
|
expect(ref.tags).toEqual([]);
|
||||||
|
expect(ref.$trustLevel).toBe('Visitor');
|
||||||
|
expect(ref.$platform).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('spreads json over defaults', () => {
|
||||||
|
const ref = createDefaultUserRef({
|
||||||
|
id: 'usr_123',
|
||||||
|
displayName: 'Test'
|
||||||
|
});
|
||||||
|
expect(ref.id).toBe('usr_123');
|
||||||
|
expect(ref.displayName).toBe('Test');
|
||||||
|
expect(ref.bio).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { createDefaultWorldRef } from '../worldTransforms';
|
||||||
|
|
||||||
|
describe('createDefaultWorldRef', () => {
|
||||||
|
it('creates object with defaults', () => {
|
||||||
|
const ref = createDefaultWorldRef({});
|
||||||
|
expect(ref.id).toBe('');
|
||||||
|
expect(ref.name).toBe('');
|
||||||
|
expect(ref.capacity).toBe(0);
|
||||||
|
expect(ref.$isLabs).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('spreads json over defaults', () => {
|
||||||
|
const ref = createDefaultWorldRef({
|
||||||
|
id: 'wrld_123',
|
||||||
|
name: 'Test World'
|
||||||
|
});
|
||||||
|
expect(ref.id).toBe('wrld_123');
|
||||||
|
expect(ref.name).toBe('Test World');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Create a default avatar ref object.
|
||||||
|
* @param {object} json - API response to merge
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
export function createDefaultAvatarRef(json) {
|
||||||
|
return {
|
||||||
|
acknowledgements: '',
|
||||||
|
authorId: '',
|
||||||
|
authorName: '',
|
||||||
|
created_at: '',
|
||||||
|
description: '',
|
||||||
|
featured: false,
|
||||||
|
highestPrice: null,
|
||||||
|
id: '',
|
||||||
|
imageUrl: '',
|
||||||
|
listingDate: null,
|
||||||
|
lock: false,
|
||||||
|
lowestPrice: null,
|
||||||
|
name: '',
|
||||||
|
pendingUpload: false,
|
||||||
|
performance: {},
|
||||||
|
productId: null,
|
||||||
|
publishedListings: [],
|
||||||
|
releaseStatus: '',
|
||||||
|
searchable: false,
|
||||||
|
styles: [],
|
||||||
|
tags: [],
|
||||||
|
thumbnailImageUrl: '',
|
||||||
|
unityPackageUrl: '',
|
||||||
|
unityPackageUrlObject: {},
|
||||||
|
unityPackages: [],
|
||||||
|
updated_at: '',
|
||||||
|
version: 0,
|
||||||
|
...json
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,31 +1,4 @@
|
|||||||
import { removeEmojis, replaceBioSymbols } from './base/string';
|
import { replaceBioSymbols } from './base/string';
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitize user JSON fields before applying to cache.
|
|
||||||
* Applies replaceBioSymbols to statusDescription, bio, note;
|
|
||||||
* removeEmojis to statusDescription;
|
|
||||||
* strips robot avatar URL.
|
|
||||||
* @param {object} json - Raw user API response
|
|
||||||
* @param {string} robotUrl - The robot/default avatar URL to strip
|
|
||||||
* @returns {object} The mutated json (same reference)
|
|
||||||
*/
|
|
||||||
export function sanitizeUserJson(json, robotUrl) {
|
|
||||||
if (json.statusDescription) {
|
|
||||||
json.statusDescription = replaceBioSymbols(json.statusDescription);
|
|
||||||
json.statusDescription = removeEmojis(json.statusDescription);
|
|
||||||
}
|
|
||||||
if (json.bio) {
|
|
||||||
json.bio = replaceBioSymbols(json.bio);
|
|
||||||
}
|
|
||||||
if (json.note) {
|
|
||||||
json.note = replaceBioSymbols(json.note);
|
|
||||||
}
|
|
||||||
if (robotUrl && json.currentAvatarImageUrl === robotUrl) {
|
|
||||||
delete json.currentAvatarImageUrl;
|
|
||||||
delete json.currentAvatarThumbnailImageUrl;
|
|
||||||
}
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitize arbitrary entity JSON fields via replaceBioSymbols.
|
* Sanitize arbitrary entity JSON fields via replaceBioSymbols.
|
||||||
@@ -42,455 +15,6 @@ export function sanitizeEntityJson(json, fields) {
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute trust level, moderator status, and troll status from user tags.
|
|
||||||
* Pure function — no store dependencies.
|
|
||||||
* @param {string[]} tags - User tags array
|
|
||||||
* @param {string} developerType - User's developerType field
|
|
||||||
* @returns {{
|
|
||||||
* trustLevel: string,
|
|
||||||
* trustClass: string,
|
|
||||||
* trustSortNum: number,
|
|
||||||
* isModerator: boolean,
|
|
||||||
* isTroll: boolean,
|
|
||||||
* isProbableTroll: boolean,
|
|
||||||
* trustColorKey: string
|
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
export function computeTrustLevel(tags, developerType) {
|
|
||||||
let isModerator = Boolean(developerType) && developerType !== 'none';
|
|
||||||
let isTroll = false;
|
|
||||||
let isProbableTroll = false;
|
|
||||||
let trustLevel = 'Visitor';
|
|
||||||
let trustClass = 'x-tag-untrusted';
|
|
||||||
let trustColorKey = 'untrusted';
|
|
||||||
let trustSortNum = 1;
|
|
||||||
|
|
||||||
if (tags.includes('admin_moderator')) {
|
|
||||||
isModerator = true;
|
|
||||||
}
|
|
||||||
if (tags.includes('system_troll')) {
|
|
||||||
isTroll = true;
|
|
||||||
}
|
|
||||||
if (tags.includes('system_probable_troll') && !isTroll) {
|
|
||||||
isProbableTroll = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tags.includes('system_trust_veteran')) {
|
|
||||||
trustLevel = 'Trusted User';
|
|
||||||
trustClass = 'x-tag-veteran';
|
|
||||||
trustColorKey = 'veteran';
|
|
||||||
trustSortNum = 5;
|
|
||||||
} else if (tags.includes('system_trust_trusted')) {
|
|
||||||
trustLevel = 'Known User';
|
|
||||||
trustClass = 'x-tag-trusted';
|
|
||||||
trustColorKey = 'trusted';
|
|
||||||
trustSortNum = 4;
|
|
||||||
} else if (tags.includes('system_trust_known')) {
|
|
||||||
trustLevel = 'User';
|
|
||||||
trustClass = 'x-tag-known';
|
|
||||||
trustColorKey = 'known';
|
|
||||||
trustSortNum = 3;
|
|
||||||
} else if (tags.includes('system_trust_basic')) {
|
|
||||||
trustLevel = 'New User';
|
|
||||||
trustClass = 'x-tag-basic';
|
|
||||||
trustColorKey = 'basic';
|
|
||||||
trustSortNum = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTroll || isProbableTroll) {
|
|
||||||
trustColorKey = 'troll';
|
|
||||||
trustSortNum += 0.1;
|
|
||||||
}
|
|
||||||
if (isModerator) {
|
|
||||||
trustColorKey = 'vip';
|
|
||||||
trustSortNum += 0.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
trustLevel,
|
|
||||||
trustClass,
|
|
||||||
trustSortNum,
|
|
||||||
isModerator,
|
|
||||||
isTroll,
|
|
||||||
isProbableTroll,
|
|
||||||
trustColorKey
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine the effective user platform.
|
|
||||||
* @param {string} platform - Current platform
|
|
||||||
* @param {string} lastPlatform - Last known platform
|
|
||||||
* @returns {string} Resolved platform
|
|
||||||
*/
|
|
||||||
export function computeUserPlatform(platform, lastPlatform) {
|
|
||||||
if (platform && platform !== 'offline' && platform !== 'web') {
|
|
||||||
return platform;
|
|
||||||
}
|
|
||||||
return lastPlatform || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute which content settings are disabled for an instance.
|
|
||||||
* @param {object} contentSettings - The instance's contentSettings object
|
|
||||||
* @param {string[]} settingsList - List of all possible content setting keys
|
|
||||||
* @returns {string[]} Array of disabled setting keys
|
|
||||||
*/
|
|
||||||
export function computeDisabledContentSettings(contentSettings, settingsList) {
|
|
||||||
const disabled = [];
|
|
||||||
if (!contentSettings || Object.keys(contentSettings).length === 0) {
|
|
||||||
return disabled;
|
|
||||||
}
|
|
||||||
for (const setting of settingsList) {
|
|
||||||
if (
|
|
||||||
typeof contentSettings[setting] === 'undefined' ||
|
|
||||||
contentSettings[setting] === true
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
disabled.push(setting);
|
|
||||||
}
|
|
||||||
return disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect which properties changed between an existing ref and incoming JSON.
|
|
||||||
* Compares primitives directly; arrays via arraysMatchFn.
|
|
||||||
* @param {object} ref - The existing cached object
|
|
||||||
* @param {object} json - The incoming update
|
|
||||||
* @param {(a: any[], b: any[]) => boolean} arraysMatchFn - Function to compare arrays
|
|
||||||
* @returns {{ hasPropChanged: boolean, changedProps: object }}
|
|
||||||
*/
|
|
||||||
export function diffObjectProps(ref, json, arraysMatchFn) {
|
|
||||||
const changedProps = {};
|
|
||||||
let hasPropChanged = false;
|
|
||||||
|
|
||||||
// Only compare primitive values
|
|
||||||
for (const prop in ref) {
|
|
||||||
if (typeof json[prop] === 'undefined') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ref[prop] === null || typeof ref[prop] !== 'object') {
|
|
||||||
changedProps[prop] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check json props against ref (including array comparison)
|
|
||||||
for (const prop in json) {
|
|
||||||
if (typeof ref[prop] === 'undefined') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (Array.isArray(json[prop]) && Array.isArray(ref[prop])) {
|
|
||||||
if (!arraysMatchFn(json[prop], ref[prop])) {
|
|
||||||
changedProps[prop] = true;
|
|
||||||
}
|
|
||||||
} else if (json[prop] === null || typeof json[prop] !== 'object') {
|
|
||||||
changedProps[prop] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve actual changes
|
|
||||||
for (const prop in changedProps) {
|
|
||||||
const asIs = ref[prop];
|
|
||||||
const toBe = json[prop];
|
|
||||||
if (asIs === toBe) {
|
|
||||||
delete changedProps[prop];
|
|
||||||
} else {
|
|
||||||
hasPropChanged = true;
|
|
||||||
changedProps[prop] = [toBe, asIs];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { hasPropChanged, changedProps };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a default user ref object with all expected fields.
|
|
||||||
* Returns a plain object (caller wraps in reactive() if needed).
|
|
||||||
* @param {object} json - API response to merge
|
|
||||||
* @returns {object} Default user object with json spread on top
|
|
||||||
*/
|
|
||||||
export function createDefaultUserRef(json) {
|
|
||||||
return {
|
|
||||||
ageVerificationStatus: '',
|
|
||||||
ageVerified: false,
|
|
||||||
allowAvatarCopying: false,
|
|
||||||
badges: [],
|
|
||||||
bio: '',
|
|
||||||
bioLinks: [],
|
|
||||||
currentAvatarImageUrl: '',
|
|
||||||
currentAvatarTags: [],
|
|
||||||
currentAvatarThumbnailImageUrl: '',
|
|
||||||
date_joined: '',
|
|
||||||
developerType: '',
|
|
||||||
discordId: '',
|
|
||||||
displayName: '',
|
|
||||||
friendKey: '',
|
|
||||||
friendRequestStatus: '',
|
|
||||||
id: '',
|
|
||||||
instanceId: '',
|
|
||||||
isFriend: false,
|
|
||||||
last_activity: '',
|
|
||||||
last_login: '',
|
|
||||||
last_mobile: null,
|
|
||||||
last_platform: '',
|
|
||||||
location: '',
|
|
||||||
platform: '',
|
|
||||||
note: null,
|
|
||||||
profilePicOverride: '',
|
|
||||||
profilePicOverrideThumbnail: '',
|
|
||||||
pronouns: '',
|
|
||||||
state: '',
|
|
||||||
status: '',
|
|
||||||
statusDescription: '',
|
|
||||||
tags: [],
|
|
||||||
travelingToInstance: '',
|
|
||||||
travelingToLocation: '',
|
|
||||||
travelingToWorld: '',
|
|
||||||
userIcon: '',
|
|
||||||
worldId: '',
|
|
||||||
// only in bulk request
|
|
||||||
fallbackAvatar: '',
|
|
||||||
// VRCX
|
|
||||||
$location: {},
|
|
||||||
$location_at: Date.now(),
|
|
||||||
$online_for: Date.now(),
|
|
||||||
$travelingToTime: Date.now(),
|
|
||||||
$offline_for: null,
|
|
||||||
$active_for: Date.now(),
|
|
||||||
$isVRCPlus: false,
|
|
||||||
$isModerator: false,
|
|
||||||
$isTroll: false,
|
|
||||||
$isProbableTroll: false,
|
|
||||||
$trustLevel: 'Visitor',
|
|
||||||
$trustClass: 'x-tag-untrusted',
|
|
||||||
$userColour: '',
|
|
||||||
$trustSortNum: 1,
|
|
||||||
$languages: [],
|
|
||||||
$joinCount: 0,
|
|
||||||
$timeSpent: 0,
|
|
||||||
$lastSeen: '',
|
|
||||||
$mutualCount: 0,
|
|
||||||
$nickName: '',
|
|
||||||
$previousLocation: '',
|
|
||||||
$customTag: '',
|
|
||||||
$customTagColour: '',
|
|
||||||
$friendNumber: 0,
|
|
||||||
$platform: '',
|
|
||||||
$moderations: {},
|
|
||||||
//
|
|
||||||
...json
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a default world ref object.
|
|
||||||
* @param {object} json - API response to merge
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
export function createDefaultWorldRef(json) {
|
|
||||||
return {
|
|
||||||
id: '',
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
defaultContentSettings: {},
|
|
||||||
authorId: '',
|
|
||||||
authorName: '',
|
|
||||||
capacity: 0,
|
|
||||||
recommendedCapacity: 0,
|
|
||||||
tags: [],
|
|
||||||
releaseStatus: '',
|
|
||||||
imageUrl: '',
|
|
||||||
thumbnailImageUrl: '',
|
|
||||||
assetUrl: '',
|
|
||||||
assetUrlObject: {},
|
|
||||||
pluginUrl: '',
|
|
||||||
pluginUrlObject: {},
|
|
||||||
unityPackageUrl: '',
|
|
||||||
unityPackageUrlObject: {},
|
|
||||||
unityPackages: [],
|
|
||||||
version: 0,
|
|
||||||
favorites: 0,
|
|
||||||
created_at: '',
|
|
||||||
updated_at: '',
|
|
||||||
publicationDate: '',
|
|
||||||
labsPublicationDate: '',
|
|
||||||
visits: 0,
|
|
||||||
popularity: 0,
|
|
||||||
heat: 0,
|
|
||||||
publicOccupants: 0,
|
|
||||||
privateOccupants: 0,
|
|
||||||
occupants: 0,
|
|
||||||
instances: [],
|
|
||||||
featured: false,
|
|
||||||
organization: '',
|
|
||||||
previewYoutubeId: '',
|
|
||||||
// VRCX
|
|
||||||
$isLabs: false,
|
|
||||||
//
|
|
||||||
...json
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a default avatar ref object.
|
|
||||||
* @param {object} json - API response to merge
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
export function createDefaultAvatarRef(json) {
|
|
||||||
return {
|
|
||||||
acknowledgements: '',
|
|
||||||
authorId: '',
|
|
||||||
authorName: '',
|
|
||||||
created_at: '',
|
|
||||||
description: '',
|
|
||||||
featured: false,
|
|
||||||
highestPrice: null,
|
|
||||||
id: '',
|
|
||||||
imageUrl: '',
|
|
||||||
listingDate: null,
|
|
||||||
lock: false,
|
|
||||||
lowestPrice: null,
|
|
||||||
name: '',
|
|
||||||
pendingUpload: false,
|
|
||||||
performance: {},
|
|
||||||
productId: null,
|
|
||||||
publishedListings: [],
|
|
||||||
releaseStatus: '',
|
|
||||||
searchable: false,
|
|
||||||
styles: [],
|
|
||||||
tags: [],
|
|
||||||
thumbnailImageUrl: '',
|
|
||||||
unityPackageUrl: '',
|
|
||||||
unityPackageUrlObject: {},
|
|
||||||
unityPackages: [],
|
|
||||||
updated_at: '',
|
|
||||||
version: 0,
|
|
||||||
...json
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a default group ref object.
|
|
||||||
* @param {object} json - API response to merge
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
export function createDefaultGroupRef(json) {
|
|
||||||
return {
|
|
||||||
id: '',
|
|
||||||
name: '',
|
|
||||||
shortCode: '',
|
|
||||||
description: '',
|
|
||||||
bannerId: '',
|
|
||||||
bannerUrl: '',
|
|
||||||
createdAt: '',
|
|
||||||
discriminator: '',
|
|
||||||
galleries: [],
|
|
||||||
iconId: '',
|
|
||||||
iconUrl: '',
|
|
||||||
isVerified: false,
|
|
||||||
joinState: '',
|
|
||||||
languages: [],
|
|
||||||
links: [],
|
|
||||||
memberCount: 0,
|
|
||||||
memberCountSyncedAt: '',
|
|
||||||
membershipStatus: '',
|
|
||||||
onlineMemberCount: 0,
|
|
||||||
ownerId: '',
|
|
||||||
privacy: '',
|
|
||||||
rules: null,
|
|
||||||
tags: [],
|
|
||||||
// in group
|
|
||||||
initialRoleIds: [],
|
|
||||||
myMember: {
|
|
||||||
bannedAt: null,
|
|
||||||
groupId: '',
|
|
||||||
has2FA: false,
|
|
||||||
id: '',
|
|
||||||
isRepresenting: false,
|
|
||||||
isSubscribedToAnnouncements: false,
|
|
||||||
joinedAt: '',
|
|
||||||
managerNotes: '',
|
|
||||||
membershipStatus: '',
|
|
||||||
permissions: [],
|
|
||||||
roleIds: [],
|
|
||||||
userId: '',
|
|
||||||
visibility: '',
|
|
||||||
_created_at: '',
|
|
||||||
_id: '',
|
|
||||||
_updated_at: ''
|
|
||||||
},
|
|
||||||
updatedAt: '',
|
|
||||||
// includeRoles: true
|
|
||||||
roles: [],
|
|
||||||
// group list
|
|
||||||
$memberId: '',
|
|
||||||
groupId: '',
|
|
||||||
isRepresenting: false,
|
|
||||||
memberVisibility: false,
|
|
||||||
mutualGroup: false,
|
|
||||||
// VRCX
|
|
||||||
$languages: [],
|
|
||||||
...json
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a default instance ref object.
|
|
||||||
* @param {object} json - API response to merge
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
export function createDefaultInstanceRef(json) {
|
|
||||||
return {
|
|
||||||
id: '',
|
|
||||||
location: '',
|
|
||||||
instanceId: '',
|
|
||||||
name: '',
|
|
||||||
worldId: '',
|
|
||||||
type: '',
|
|
||||||
ownerId: '',
|
|
||||||
tags: [],
|
|
||||||
active: false,
|
|
||||||
full: false,
|
|
||||||
n_users: 0,
|
|
||||||
hasCapacityForYou: true, // not present depending on endpoint
|
|
||||||
capacity: 0,
|
|
||||||
recommendedCapacity: 0,
|
|
||||||
userCount: 0,
|
|
||||||
queueEnabled: false, // only present with group instance type
|
|
||||||
queueSize: 0, // only present when queuing is enabled
|
|
||||||
platforms: {},
|
|
||||||
gameServerVersion: 0,
|
|
||||||
hardClose: null, // boolean or null
|
|
||||||
closedAt: null, // string or null
|
|
||||||
secureName: '',
|
|
||||||
shortName: '',
|
|
||||||
world: {},
|
|
||||||
users: [], // only present when you're the owner
|
|
||||||
clientNumber: '',
|
|
||||||
contentSettings: {},
|
|
||||||
photonRegion: '',
|
|
||||||
region: '',
|
|
||||||
canRequestInvite: false,
|
|
||||||
permanent: false,
|
|
||||||
private: '', // part of instance tag
|
|
||||||
hidden: '', // part of instance tag
|
|
||||||
nonce: '', // only present when you're the owner
|
|
||||||
strict: false, // deprecated
|
|
||||||
displayName: null,
|
|
||||||
groupAccessType: null, // only present with group instance type
|
|
||||||
roleRestricted: false, // only present with group instance type
|
|
||||||
instancePersistenceEnabled: null,
|
|
||||||
playerPersistenceEnabled: null,
|
|
||||||
ageGate: null,
|
|
||||||
// VRCX
|
|
||||||
$fetchedAt: '',
|
|
||||||
$disabledContentSettings: [],
|
|
||||||
...json
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a default favorite group ref from JSON data.
|
* Build a default favorite group ref from JSON data.
|
||||||
* @param {object} json
|
* @param {object} json
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Create a default group ref object.
|
||||||
|
* @param {object} json - API response to merge
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
export function createDefaultGroupRef(json) {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
shortCode: '',
|
||||||
|
description: '',
|
||||||
|
bannerId: '',
|
||||||
|
bannerUrl: '',
|
||||||
|
createdAt: '',
|
||||||
|
discriminator: '',
|
||||||
|
galleries: [],
|
||||||
|
iconId: '',
|
||||||
|
iconUrl: '',
|
||||||
|
isVerified: false,
|
||||||
|
joinState: '',
|
||||||
|
languages: [],
|
||||||
|
links: [],
|
||||||
|
memberCount: 0,
|
||||||
|
memberCountSyncedAt: '',
|
||||||
|
membershipStatus: '',
|
||||||
|
onlineMemberCount: 0,
|
||||||
|
ownerId: '',
|
||||||
|
privacy: '',
|
||||||
|
rules: null,
|
||||||
|
tags: [],
|
||||||
|
// in group
|
||||||
|
initialRoleIds: [],
|
||||||
|
myMember: {
|
||||||
|
bannedAt: null,
|
||||||
|
groupId: '',
|
||||||
|
has2FA: false,
|
||||||
|
id: '',
|
||||||
|
isRepresenting: false,
|
||||||
|
isSubscribedToAnnouncements: false,
|
||||||
|
joinedAt: '',
|
||||||
|
managerNotes: '',
|
||||||
|
membershipStatus: '',
|
||||||
|
permissions: [],
|
||||||
|
roleIds: [],
|
||||||
|
userId: '',
|
||||||
|
visibility: '',
|
||||||
|
_created_at: '',
|
||||||
|
_id: '',
|
||||||
|
_updated_at: ''
|
||||||
|
},
|
||||||
|
updatedAt: '',
|
||||||
|
// includeRoles: true
|
||||||
|
roles: [],
|
||||||
|
// group list
|
||||||
|
$memberId: '',
|
||||||
|
groupId: '',
|
||||||
|
isRepresenting: false,
|
||||||
|
memberVisibility: false,
|
||||||
|
mutualGroup: false,
|
||||||
|
// VRCX
|
||||||
|
$languages: [],
|
||||||
|
...json
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -25,6 +25,11 @@ export * from './throttle';
|
|||||||
export * from './retry';
|
export * from './retry';
|
||||||
export * from './gameLog';
|
export * from './gameLog';
|
||||||
export * from './entityTransforms';
|
export * from './entityTransforms';
|
||||||
|
export * from './userTransforms';
|
||||||
|
export * from './worldTransforms';
|
||||||
|
export * from './avatarTransforms';
|
||||||
|
export * from './groupTransforms';
|
||||||
|
export * from './instanceTransforms';
|
||||||
export * from './cacheUtils';
|
export * from './cacheUtils';
|
||||||
export * from './notificationTransforms';
|
export * from './notificationTransforms';
|
||||||
export * from './discordPresence';
|
export * from './discordPresence';
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* Compute which content settings are disabled for an instance.
|
||||||
|
* @param {object} contentSettings - The instance's contentSettings object
|
||||||
|
* @param {string[]} settingsList - List of all possible content setting keys
|
||||||
|
* @returns {string[]} Array of disabled setting keys
|
||||||
|
*/
|
||||||
|
export function computeDisabledContentSettings(contentSettings, settingsList) {
|
||||||
|
const disabled = [];
|
||||||
|
if (!contentSettings || Object.keys(contentSettings).length === 0) {
|
||||||
|
return disabled;
|
||||||
|
}
|
||||||
|
for (const setting of settingsList) {
|
||||||
|
if (
|
||||||
|
typeof contentSettings[setting] === 'undefined' ||
|
||||||
|
contentSettings[setting] === true
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
disabled.push(setting);
|
||||||
|
}
|
||||||
|
return disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a default instance ref object.
|
||||||
|
* @param {object} json - API response to merge
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
export function createDefaultInstanceRef(json) {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
location: '',
|
||||||
|
instanceId: '',
|
||||||
|
name: '',
|
||||||
|
worldId: '',
|
||||||
|
type: '',
|
||||||
|
ownerId: '',
|
||||||
|
tags: [],
|
||||||
|
active: false,
|
||||||
|
full: false,
|
||||||
|
n_users: 0,
|
||||||
|
hasCapacityForYou: true, // not present depending on endpoint
|
||||||
|
capacity: 0,
|
||||||
|
recommendedCapacity: 0,
|
||||||
|
userCount: 0,
|
||||||
|
queueEnabled: false, // only present with group instance type
|
||||||
|
queueSize: 0, // only present when queuing is enabled
|
||||||
|
platforms: {},
|
||||||
|
gameServerVersion: 0,
|
||||||
|
hardClose: null, // boolean or null
|
||||||
|
closedAt: null, // string or null
|
||||||
|
secureName: '',
|
||||||
|
shortName: '',
|
||||||
|
world: {},
|
||||||
|
users: [], // only present when you're the owner
|
||||||
|
clientNumber: '',
|
||||||
|
contentSettings: {},
|
||||||
|
photonRegion: '',
|
||||||
|
region: '',
|
||||||
|
canRequestInvite: false,
|
||||||
|
permanent: false,
|
||||||
|
private: '', // part of instance tag
|
||||||
|
hidden: '', // part of instance tag
|
||||||
|
nonce: '', // only present when you're the owner
|
||||||
|
strict: false, // deprecated
|
||||||
|
displayName: null,
|
||||||
|
groupAccessType: null, // only present with group instance type
|
||||||
|
roleRestricted: false, // only present with group instance type
|
||||||
|
instancePersistenceEnabled: null,
|
||||||
|
playerPersistenceEnabled: null,
|
||||||
|
ageGate: null,
|
||||||
|
// VRCX
|
||||||
|
$fetchedAt: '',
|
||||||
|
$disabledContentSettings: [],
|
||||||
|
...json
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
import { removeEmojis, replaceBioSymbols } from './base/string';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize user JSON fields before applying to cache.
|
||||||
|
* Applies replaceBioSymbols to statusDescription, bio, note;
|
||||||
|
* removeEmojis to statusDescription;
|
||||||
|
* strips robot avatar URL.
|
||||||
|
* @param {object} json - Raw user API response
|
||||||
|
* @param {string} robotUrl - The robot/default avatar URL to strip
|
||||||
|
* @returns {object} The mutated json (same reference)
|
||||||
|
*/
|
||||||
|
export function sanitizeUserJson(json, robotUrl) {
|
||||||
|
if (json.statusDescription) {
|
||||||
|
json.statusDescription = replaceBioSymbols(json.statusDescription);
|
||||||
|
json.statusDescription = removeEmojis(json.statusDescription);
|
||||||
|
}
|
||||||
|
if (json.bio) {
|
||||||
|
json.bio = replaceBioSymbols(json.bio);
|
||||||
|
}
|
||||||
|
if (json.note) {
|
||||||
|
json.note = replaceBioSymbols(json.note);
|
||||||
|
}
|
||||||
|
if (robotUrl && json.currentAvatarImageUrl === robotUrl) {
|
||||||
|
delete json.currentAvatarImageUrl;
|
||||||
|
delete json.currentAvatarThumbnailImageUrl;
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute trust level, moderator status, and troll status from user tags.
|
||||||
|
* Pure function — no store dependencies.
|
||||||
|
* @param {string[]} tags - User tags array
|
||||||
|
* @param {string} developerType - User's developerType field
|
||||||
|
* @returns {{
|
||||||
|
* trustLevel: string,
|
||||||
|
* trustClass: string,
|
||||||
|
* trustSortNum: number,
|
||||||
|
* isModerator: boolean,
|
||||||
|
* isTroll: boolean,
|
||||||
|
* isProbableTroll: boolean,
|
||||||
|
* trustColorKey: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function computeTrustLevel(tags, developerType) {
|
||||||
|
let isModerator = Boolean(developerType) && developerType !== 'none';
|
||||||
|
let isTroll = false;
|
||||||
|
let isProbableTroll = false;
|
||||||
|
let trustLevel = 'Visitor';
|
||||||
|
let trustClass = 'x-tag-untrusted';
|
||||||
|
let trustColorKey = 'untrusted';
|
||||||
|
let trustSortNum = 1;
|
||||||
|
|
||||||
|
if (tags.includes('admin_moderator')) {
|
||||||
|
isModerator = true;
|
||||||
|
}
|
||||||
|
if (tags.includes('system_troll')) {
|
||||||
|
isTroll = true;
|
||||||
|
}
|
||||||
|
if (tags.includes('system_probable_troll') && !isTroll) {
|
||||||
|
isProbableTroll = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tags.includes('system_trust_veteran')) {
|
||||||
|
trustLevel = 'Trusted User';
|
||||||
|
trustClass = 'x-tag-veteran';
|
||||||
|
trustColorKey = 'veteran';
|
||||||
|
trustSortNum = 5;
|
||||||
|
} else if (tags.includes('system_trust_trusted')) {
|
||||||
|
trustLevel = 'Known User';
|
||||||
|
trustClass = 'x-tag-trusted';
|
||||||
|
trustColorKey = 'trusted';
|
||||||
|
trustSortNum = 4;
|
||||||
|
} else if (tags.includes('system_trust_known')) {
|
||||||
|
trustLevel = 'User';
|
||||||
|
trustClass = 'x-tag-known';
|
||||||
|
trustColorKey = 'known';
|
||||||
|
trustSortNum = 3;
|
||||||
|
} else if (tags.includes('system_trust_basic')) {
|
||||||
|
trustLevel = 'New User';
|
||||||
|
trustClass = 'x-tag-basic';
|
||||||
|
trustColorKey = 'basic';
|
||||||
|
trustSortNum = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTroll || isProbableTroll) {
|
||||||
|
trustColorKey = 'troll';
|
||||||
|
trustSortNum += 0.1;
|
||||||
|
}
|
||||||
|
if (isModerator) {
|
||||||
|
trustColorKey = 'vip';
|
||||||
|
trustSortNum += 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
trustLevel,
|
||||||
|
trustClass,
|
||||||
|
trustSortNum,
|
||||||
|
isModerator,
|
||||||
|
isTroll,
|
||||||
|
isProbableTroll,
|
||||||
|
trustColorKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the effective user platform.
|
||||||
|
* @param {string} platform - Current platform
|
||||||
|
* @param {string} lastPlatform - Last known platform
|
||||||
|
* @returns {string} Resolved platform
|
||||||
|
*/
|
||||||
|
export function computeUserPlatform(platform, lastPlatform) {
|
||||||
|
if (platform && platform !== 'offline' && platform !== 'web') {
|
||||||
|
return platform;
|
||||||
|
}
|
||||||
|
return lastPlatform || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect which properties changed between an existing ref and incoming JSON.
|
||||||
|
* Compares primitives directly; arrays via arraysMatchFn.
|
||||||
|
* @param {object} ref - The existing cached object
|
||||||
|
* @param {object} json - The incoming update
|
||||||
|
* @param {(a: any[], b: any[]) => boolean} arraysMatchFn - Function to compare arrays
|
||||||
|
* @returns {{ hasPropChanged: boolean, changedProps: object }}
|
||||||
|
*/
|
||||||
|
export function diffObjectProps(ref, json, arraysMatchFn) {
|
||||||
|
const changedProps = {};
|
||||||
|
let hasPropChanged = false;
|
||||||
|
|
||||||
|
// Only compare primitive values
|
||||||
|
for (const prop in ref) {
|
||||||
|
if (typeof json[prop] === 'undefined') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ref[prop] === null || typeof ref[prop] !== 'object') {
|
||||||
|
changedProps[prop] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check json props against ref (including array comparison)
|
||||||
|
for (const prop in json) {
|
||||||
|
if (typeof ref[prop] === 'undefined') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Array.isArray(json[prop]) && Array.isArray(ref[prop])) {
|
||||||
|
if (!arraysMatchFn(json[prop], ref[prop])) {
|
||||||
|
changedProps[prop] = true;
|
||||||
|
}
|
||||||
|
} else if (json[prop] === null || typeof json[prop] !== 'object') {
|
||||||
|
changedProps[prop] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve actual changes
|
||||||
|
for (const prop in changedProps) {
|
||||||
|
const asIs = ref[prop];
|
||||||
|
const toBe = json[prop];
|
||||||
|
if (asIs === toBe) {
|
||||||
|
delete changedProps[prop];
|
||||||
|
} else {
|
||||||
|
hasPropChanged = true;
|
||||||
|
changedProps[prop] = [toBe, asIs];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { hasPropChanged, changedProps };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a default user ref object with all expected fields.
|
||||||
|
* Returns a plain object (caller wraps in reactive() if needed).
|
||||||
|
* @param {object} json - API response to merge
|
||||||
|
* @returns {object} Default user object with json spread on top
|
||||||
|
*/
|
||||||
|
export function createDefaultUserRef(json) {
|
||||||
|
return {
|
||||||
|
ageVerificationStatus: '',
|
||||||
|
ageVerified: false,
|
||||||
|
allowAvatarCopying: false,
|
||||||
|
badges: [],
|
||||||
|
bio: '',
|
||||||
|
bioLinks: [],
|
||||||
|
currentAvatarImageUrl: '',
|
||||||
|
currentAvatarTags: [],
|
||||||
|
currentAvatarThumbnailImageUrl: '',
|
||||||
|
date_joined: '',
|
||||||
|
developerType: '',
|
||||||
|
discordId: '',
|
||||||
|
displayName: '',
|
||||||
|
friendKey: '',
|
||||||
|
friendRequestStatus: '',
|
||||||
|
id: '',
|
||||||
|
instanceId: '',
|
||||||
|
isFriend: false,
|
||||||
|
last_activity: '',
|
||||||
|
last_login: '',
|
||||||
|
last_mobile: null,
|
||||||
|
last_platform: '',
|
||||||
|
location: '',
|
||||||
|
platform: '',
|
||||||
|
note: null,
|
||||||
|
profilePicOverride: '',
|
||||||
|
profilePicOverrideThumbnail: '',
|
||||||
|
pronouns: '',
|
||||||
|
state: '',
|
||||||
|
status: '',
|
||||||
|
statusDescription: '',
|
||||||
|
tags: [],
|
||||||
|
travelingToInstance: '',
|
||||||
|
travelingToLocation: '',
|
||||||
|
travelingToWorld: '',
|
||||||
|
userIcon: '',
|
||||||
|
worldId: '',
|
||||||
|
// only in bulk request
|
||||||
|
fallbackAvatar: '',
|
||||||
|
// VRCX
|
||||||
|
$location: {},
|
||||||
|
$location_at: Date.now(),
|
||||||
|
$online_for: Date.now(),
|
||||||
|
$travelingToTime: Date.now(),
|
||||||
|
$offline_for: null,
|
||||||
|
$active_for: Date.now(),
|
||||||
|
$isVRCPlus: false,
|
||||||
|
$isModerator: false,
|
||||||
|
$isTroll: false,
|
||||||
|
$isProbableTroll: false,
|
||||||
|
$trustLevel: 'Visitor',
|
||||||
|
$trustClass: 'x-tag-untrusted',
|
||||||
|
$userColour: '',
|
||||||
|
$trustSortNum: 1,
|
||||||
|
$languages: [],
|
||||||
|
$joinCount: 0,
|
||||||
|
$timeSpent: 0,
|
||||||
|
$lastSeen: '',
|
||||||
|
$mutualCount: 0,
|
||||||
|
$nickName: '',
|
||||||
|
$previousLocation: '',
|
||||||
|
$customTag: '',
|
||||||
|
$customTagColour: '',
|
||||||
|
$friendNumber: 0,
|
||||||
|
$platform: '',
|
||||||
|
$moderations: {},
|
||||||
|
//
|
||||||
|
...json
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Create a default world ref object.
|
||||||
|
* @param {object} json - API response to merge
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
export function createDefaultWorldRef(json) {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
defaultContentSettings: {},
|
||||||
|
authorId: '',
|
||||||
|
authorName: '',
|
||||||
|
capacity: 0,
|
||||||
|
recommendedCapacity: 0,
|
||||||
|
tags: [],
|
||||||
|
releaseStatus: '',
|
||||||
|
imageUrl: '',
|
||||||
|
thumbnailImageUrl: '',
|
||||||
|
assetUrl: '',
|
||||||
|
assetUrlObject: {},
|
||||||
|
pluginUrl: '',
|
||||||
|
pluginUrlObject: {},
|
||||||
|
unityPackageUrl: '',
|
||||||
|
unityPackageUrlObject: {},
|
||||||
|
unityPackages: [],
|
||||||
|
version: 0,
|
||||||
|
favorites: 0,
|
||||||
|
created_at: '',
|
||||||
|
updated_at: '',
|
||||||
|
publicationDate: '',
|
||||||
|
labsPublicationDate: '',
|
||||||
|
visits: 0,
|
||||||
|
popularity: 0,
|
||||||
|
heat: 0,
|
||||||
|
publicOccupants: 0,
|
||||||
|
privateOccupants: 0,
|
||||||
|
occupants: 0,
|
||||||
|
instances: [],
|
||||||
|
featured: false,
|
||||||
|
organization: '',
|
||||||
|
previewYoutubeId: '',
|
||||||
|
// VRCX
|
||||||
|
$isLabs: false,
|
||||||
|
//
|
||||||
|
...json
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user