mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-15 12:53:51 +02:00
2725 lines
89 KiB
JavaScript
2725 lines
89 KiB
JavaScript
import { computed, ref, watch } from 'vue';
|
|
import { defineStore } from 'pinia';
|
|
import { toast } from 'vue-sonner';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import Noty from 'noty';
|
|
import dayjs from 'dayjs';
|
|
|
|
import {
|
|
checkCanInvite,
|
|
displayLocation,
|
|
escapeTag,
|
|
extractFileId,
|
|
extractFileVersion,
|
|
getUserMemo,
|
|
parseLocation,
|
|
removeFromArray,
|
|
replaceBioSymbols
|
|
} from '../shared/utils';
|
|
import {
|
|
friendRequest,
|
|
instanceRequest,
|
|
notificationRequest,
|
|
userRequest,
|
|
worldRequest
|
|
} from '../api';
|
|
import { database, dbVars } from '../service/database';
|
|
import { AppDebug } from '../service/appConfig';
|
|
import { useAdvancedSettingsStore } from './settings/advanced';
|
|
import { useAppearanceSettingsStore } from './settings/appearance';
|
|
import { useFavoriteStore } from './favorite';
|
|
import { useFriendStore } from './friend';
|
|
import { useGameStore } from './game';
|
|
import { useGeneralSettingsStore } from './settings/general';
|
|
import { useGroupStore } from './group';
|
|
import { useInstanceStore } from './instance';
|
|
import { useLocationStore } from './location';
|
|
import { useModalStore } from './modal';
|
|
import { useNotificationsSettingsStore } from './settings/notifications';
|
|
import { useSharedFeedStore } from './sharedFeed';
|
|
import { useUiStore } from './ui';
|
|
import { useUserStore } from './user';
|
|
import { useWristOverlaySettingsStore } from './settings/wristOverlay';
|
|
import { watchState } from '../service/watchState';
|
|
|
|
import configRepository from '../service/config';
|
|
|
|
export const useNotificationStore = defineStore('Notification', () => {
|
|
const { t } = useI18n();
|
|
const generalSettingsStore = useGeneralSettingsStore();
|
|
const locationStore = useLocationStore();
|
|
const favoriteStore = useFavoriteStore();
|
|
const friendStore = useFriendStore();
|
|
const notificationsSettingsStore = useNotificationsSettingsStore();
|
|
const advancedSettingsStore = useAdvancedSettingsStore();
|
|
const appearanceSettingsStore = useAppearanceSettingsStore();
|
|
const userStore = useUserStore();
|
|
const wristOverlaySettingsStore = useWristOverlaySettingsStore();
|
|
const uiStore = useUiStore();
|
|
const gameStore = useGameStore();
|
|
const sharedFeedStore = useSharedFeedStore();
|
|
const instanceStore = useInstanceStore();
|
|
const modalStore = useModalStore();
|
|
const groupStore = useGroupStore();
|
|
|
|
const notificationInitStatus = ref(false);
|
|
const notificationTable = ref({
|
|
data: [],
|
|
filters: [
|
|
{
|
|
prop: 'type',
|
|
value: []
|
|
},
|
|
{
|
|
prop: ['senderUsername', 'message'],
|
|
value: ''
|
|
}
|
|
],
|
|
pageSize: 20,
|
|
pageSizeLinked: true,
|
|
paginationProps: {
|
|
layout: 'sizes,prev,pager,next,total'
|
|
}
|
|
});
|
|
const unseenNotifications = ref([]);
|
|
const isNotificationsLoading = ref(false);
|
|
const isNotificationCenterOpen = ref(false);
|
|
|
|
const FRIEND_TYPES = new Set([
|
|
'friendRequest',
|
|
'ignoredFriendRequest',
|
|
'invite',
|
|
'requestInvite',
|
|
'inviteResponse',
|
|
'requestInviteResponse',
|
|
'boop'
|
|
]);
|
|
const GROUP_TYPES_PREFIX = ['group.', 'moderation.'];
|
|
const GROUP_EXACT_TYPES = new Set(['groupChange', 'event.announcement']);
|
|
|
|
function getNotificationCategory(type) {
|
|
if (!type) return 'other';
|
|
if (FRIEND_TYPES.has(type)) return 'friend';
|
|
if (
|
|
GROUP_EXACT_TYPES.has(type) ||
|
|
GROUP_TYPES_PREFIX.some((p) => type.startsWith(p))
|
|
)
|
|
return 'group';
|
|
return 'other';
|
|
}
|
|
|
|
const friendNotifications = computed(() =>
|
|
notificationTable.value.data.filter(
|
|
(n) => getNotificationCategory(n.type) === 'friend'
|
|
)
|
|
);
|
|
const groupNotifications = computed(() =>
|
|
notificationTable.value.data.filter(
|
|
(n) => getNotificationCategory(n.type) === 'group'
|
|
)
|
|
);
|
|
const otherNotifications = computed(() =>
|
|
notificationTable.value.data.filter(
|
|
(n) => getNotificationCategory(n.type) === 'other'
|
|
)
|
|
);
|
|
const unseenSet = computed(() => new Set(unseenNotifications.value));
|
|
const unseenFriendNotifications = computed(() =>
|
|
friendNotifications.value.filter((n) => unseenSet.value.has(n.id))
|
|
);
|
|
const unseenGroupNotifications = computed(() =>
|
|
groupNotifications.value.filter((n) => unseenSet.value.has(n.id))
|
|
);
|
|
const unseenOtherNotifications = computed(() =>
|
|
otherNotifications.value.filter((n) => unseenSet.value.has(n.id))
|
|
);
|
|
const hasUnseenNotifications = computed(
|
|
() => unseenNotifications.value.length > 0
|
|
);
|
|
|
|
const notyMap = {};
|
|
|
|
watch(
|
|
() => watchState.isLoggedIn,
|
|
(isLoggedIn) => {
|
|
isNotificationsLoading.value = false;
|
|
notificationTable.value.data = [];
|
|
if (isLoggedIn) {
|
|
initNotifications();
|
|
}
|
|
},
|
|
{ flush: 'sync' }
|
|
);
|
|
|
|
async function init() {
|
|
notificationTable.value.filters[0].value = JSON.parse(
|
|
await configRepository.getString(
|
|
'VRCX_notificationTableFilters',
|
|
'[]'
|
|
)
|
|
);
|
|
}
|
|
|
|
init();
|
|
|
|
function handleNotification(args) {
|
|
args.ref = applyNotification(args.json);
|
|
const { ref } = args;
|
|
const array = notificationTable.value.data;
|
|
const { length } = array;
|
|
if (ref.seen) {
|
|
removeFromArray(unseenNotifications.value, ref.id);
|
|
} else if (!unseenNotifications.value.includes(ref.id)) {
|
|
unseenNotifications.value.push(ref.id);
|
|
}
|
|
for (let i = 0; i < length; ++i) {
|
|
if (array[i].id === ref.id) {
|
|
array[i] = ref;
|
|
return;
|
|
}
|
|
}
|
|
if (ref.senderUserId !== userStore.currentUser.id) {
|
|
if (
|
|
ref.type !== 'friendRequest' &&
|
|
ref.type !== 'ignoredFriendRequest' &&
|
|
!ref.type.includes('.')
|
|
) {
|
|
database.addNotificationToDatabase(ref);
|
|
}
|
|
if (watchState.isFriendsLoaded && notificationInitStatus.value) {
|
|
if (
|
|
ref.details?.worldId &&
|
|
!instanceStore.cachedInstances.has(ref.details.worldId)
|
|
) {
|
|
// get instance name for invite
|
|
const L = parseLocation(ref.details.worldId);
|
|
if (L.isRealInstance) {
|
|
instanceRequest.getCachedInstance({
|
|
worldId: L.worldId,
|
|
instanceId: L.instanceId
|
|
});
|
|
}
|
|
}
|
|
if (
|
|
notificationTable.value.filters[0].value.length === 0 ||
|
|
notificationTable.value.filters[0].value.includes(ref.type)
|
|
) {
|
|
uiStore.notifyMenu('notification');
|
|
}
|
|
queueNotificationNoty(ref);
|
|
sharedFeedStore.addEntry(ref);
|
|
}
|
|
}
|
|
notificationTable.value.data.push(ref);
|
|
const D = userStore.userDialog;
|
|
if (
|
|
D.visible === false ||
|
|
ref.type !== 'friendRequest' ||
|
|
ref.senderUserId !== D.id
|
|
) {
|
|
return;
|
|
}
|
|
D.incomingRequest = true;
|
|
}
|
|
|
|
function handleNotificationHide(notificationId) {
|
|
const ref = notificationTable.value.data.find(
|
|
(n) => n.id === notificationId
|
|
);
|
|
if (typeof ref === 'undefined') {
|
|
return;
|
|
}
|
|
if (
|
|
ref.type === 'friendRequest' ||
|
|
ref.type === 'ignoredFriendRequest' ||
|
|
ref.type.includes('.')
|
|
) {
|
|
removeFromArray(notificationTable.value.data, ref);
|
|
} else {
|
|
ref.$isExpired = true;
|
|
database.updateNotificationExpired(ref);
|
|
}
|
|
handleNotificationExpire({
|
|
ref,
|
|
params: {
|
|
notificationId: ref.id
|
|
}
|
|
});
|
|
}
|
|
|
|
function handlePipelineNotification(args) {
|
|
const ref = args.json;
|
|
if (
|
|
ref.type !== 'requestInvite' ||
|
|
generalSettingsStore.autoAcceptInviteRequests === 'Off'
|
|
) {
|
|
return;
|
|
}
|
|
|
|
let currentLocation = locationStore.lastLocation.location;
|
|
if (locationStore.lastLocation.location === 'traveling') {
|
|
currentLocation = locationStore.lastLocationDestination;
|
|
}
|
|
if (!currentLocation) {
|
|
// game log disabled, use API location
|
|
currentLocation = userStore.currentUser.$locationTag;
|
|
if (userStore.currentUser.$travelingToLocation) {
|
|
currentLocation = userStore.currentUser.$travelingToLocation;
|
|
}
|
|
}
|
|
if (!currentLocation) {
|
|
return;
|
|
}
|
|
if (
|
|
generalSettingsStore.autoAcceptInviteRequests === 'All Favorites' &&
|
|
!favoriteStore.state.favoriteFriends_.some(
|
|
(x) => x.id === ref.senderUserId
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
if (
|
|
generalSettingsStore.autoAcceptInviteRequests ===
|
|
'Selected Favorites'
|
|
) {
|
|
const groups = generalSettingsStore.autoAcceptInviteGroups;
|
|
if (groups.length === 0) {
|
|
return;
|
|
} else {
|
|
let found = false;
|
|
for (const groupKey of groups) {
|
|
if (groupKey.startsWith('local:')) {
|
|
const localGroup = groupKey.slice(6);
|
|
const localFavs =
|
|
favoriteStore.localFriendFavorites.get(localGroup);
|
|
if (localFavs && localFavs.has(ref.senderUserId)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
} else {
|
|
const remoteFavs =
|
|
favoriteStore.cachedFavorites.get(groupKey);
|
|
if (
|
|
remoteFavs &&
|
|
remoteFavs.some(
|
|
(f) => f.favoriteId === ref.senderUserId
|
|
)
|
|
) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!found) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (!checkCanInvite(currentLocation)) {
|
|
return;
|
|
}
|
|
|
|
const L = parseLocation(currentLocation);
|
|
worldRequest
|
|
.getCachedWorld({
|
|
worldId: L.worldId
|
|
})
|
|
.then((args1) => {
|
|
notificationRequest
|
|
.sendInvite(
|
|
{
|
|
instanceId: L.tag,
|
|
worldId: L.tag,
|
|
worldName: args1.ref.name,
|
|
rsvp: true
|
|
},
|
|
ref.senderUserId
|
|
)
|
|
.then((_args) => {
|
|
const text = `Auto invite sent to ${ref.senderUsername}`;
|
|
if (AppDebug.errorNoty) {
|
|
AppDebug.errorNoty.close();
|
|
}
|
|
AppDebug.errorNoty = new Noty({
|
|
type: 'info',
|
|
text
|
|
});
|
|
AppDebug.errorNoty.show();
|
|
console.log(text);
|
|
notificationRequest.hideNotification({
|
|
notificationId: ref.id
|
|
});
|
|
return _args;
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
});
|
|
});
|
|
}
|
|
|
|
function handleNotificationSee(notificationId) {
|
|
removeFromArray(unseenNotifications.value, notificationId);
|
|
if (unseenNotifications.value.length === 0) {
|
|
uiStore.removeNotify('notification');
|
|
}
|
|
const ref = notificationTable.value.data.find(
|
|
(n) => n.id === notificationId
|
|
);
|
|
if (ref) {
|
|
ref.seen = true;
|
|
}
|
|
database.seenNotificationV2(ref);
|
|
}
|
|
|
|
function handleNotificationAccept(args) {
|
|
let ref;
|
|
const array = notificationTable.value.data;
|
|
for (let i = array.length - 1; i >= 0; i--) {
|
|
if (array[i].id === args.params.notificationId) {
|
|
ref = array[i];
|
|
break;
|
|
}
|
|
}
|
|
if (typeof ref === 'undefined') {
|
|
return;
|
|
}
|
|
ref.$isExpired = true;
|
|
args.ref = ref;
|
|
handleNotificationExpire({
|
|
ref,
|
|
params: {
|
|
notificationId: ref.id
|
|
}
|
|
});
|
|
friendStore.handleFriendAdd({
|
|
params: {
|
|
userId: ref.senderUserId
|
|
}
|
|
});
|
|
|
|
const D = userStore.userDialog;
|
|
if (
|
|
D.visible === false ||
|
|
typeof args.ref === 'undefined' ||
|
|
args.ref.type !== 'friendRequest' ||
|
|
args.ref.senderUserId !== D.id
|
|
) {
|
|
return;
|
|
}
|
|
D.isFriend = true;
|
|
}
|
|
|
|
function handleNotificationExpire(args) {
|
|
const { ref } = args;
|
|
const D = userStore.userDialog;
|
|
if (
|
|
D.visible === false ||
|
|
ref.type !== 'friendRequest' ||
|
|
ref.senderUserId !== D.id
|
|
) {
|
|
return;
|
|
}
|
|
D.incomingRequest = false;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {object} data
|
|
* @returns {object}
|
|
*/
|
|
function applyNotification(data) {
|
|
const json = { ...data };
|
|
if (json.message) {
|
|
json.message = replaceBioSymbols(json.message);
|
|
}
|
|
let ref;
|
|
const array = notificationTable.value.data;
|
|
for (let i = array.length - 1; i >= 0; i--) {
|
|
if (array[i].id === json.id) {
|
|
ref = array[i];
|
|
break;
|
|
}
|
|
}
|
|
// delete any null in json
|
|
for (const key in json) {
|
|
if (json[key] === null) {
|
|
delete json[key];
|
|
}
|
|
}
|
|
if (typeof ref === 'undefined') {
|
|
ref = {
|
|
id: '',
|
|
senderUserId: '',
|
|
senderUsername: '',
|
|
type: '',
|
|
message: '',
|
|
details: {},
|
|
seen: false,
|
|
created_at: '',
|
|
// VRCX
|
|
$isExpired: false,
|
|
//
|
|
...json
|
|
};
|
|
} else {
|
|
Object.assign(ref, json);
|
|
ref.$isExpired = false;
|
|
}
|
|
if (ref.details !== Object(ref.details)) {
|
|
let details = {};
|
|
if (ref.details !== '{}') {
|
|
try {
|
|
const object = JSON.parse(ref.details);
|
|
if (object === Object(object)) {
|
|
details = object;
|
|
}
|
|
} catch (err) {
|
|
console.log(err);
|
|
}
|
|
}
|
|
ref.details = details;
|
|
}
|
|
return ref;
|
|
}
|
|
|
|
function applyNotificationV2(data) {
|
|
const json = { ...data };
|
|
// delete any null in json
|
|
for (const key in json) {
|
|
if (json[key] === null || typeof json[key] === 'undefined') {
|
|
delete json[key];
|
|
}
|
|
}
|
|
if (json.message) {
|
|
json.message = replaceBioSymbols(json.message);
|
|
}
|
|
if (json.title) {
|
|
json.title = replaceBioSymbols(json.title);
|
|
}
|
|
let ref = notificationTable.value.data.find((n) => n.id === json.id);
|
|
if (typeof ref === 'undefined') {
|
|
ref = {
|
|
id: '',
|
|
createdAt: '',
|
|
updatedAt: '',
|
|
expiresAt: '',
|
|
type: '',
|
|
link: '',
|
|
linkText: '',
|
|
message: '',
|
|
title: '',
|
|
imageUrl: '',
|
|
seen: false,
|
|
senderUserId: '',
|
|
senderUsername: '',
|
|
data: {},
|
|
responses: [],
|
|
details: {},
|
|
version: 2,
|
|
...json
|
|
};
|
|
} else {
|
|
Object.assign(ref, json);
|
|
}
|
|
ref.created_at = ref.createdAt; // for table
|
|
// legacy handling of boops
|
|
if (ref.type === 'boop' && ref.title) {
|
|
ref.message = ref.title;
|
|
ref.title = '';
|
|
if (ref.details?.emojiId?.startsWith('default_')) {
|
|
ref.imageUrl = ref.details.emojiId;
|
|
ref.message += ` ${ref.details.emojiId.replace('default_', '')}`;
|
|
} else {
|
|
ref.imageUrl = `${AppDebug.endpointDomain}/file/${ref.details.emojiId}/${ref.details.emojiVersion}`;
|
|
}
|
|
}
|
|
return ref;
|
|
}
|
|
|
|
function handleNotificationV2(args) {
|
|
const ref = applyNotificationV2(args.json);
|
|
if (ref.seen) {
|
|
removeFromArray(unseenNotifications.value, ref.id);
|
|
} else if (!unseenNotifications.value.includes(ref.id)) {
|
|
unseenNotifications.value.push(ref.id);
|
|
}
|
|
const existingNotification = notificationTable.value.data.find(
|
|
(n) => n.id === ref.id
|
|
);
|
|
if (existingNotification) {
|
|
Object.assign(existingNotification, ref);
|
|
database.addNotificationV2ToDatabase(existingNotification); // update
|
|
return;
|
|
}
|
|
|
|
if (
|
|
notificationTable.value.filters[0].value.length === 0 ||
|
|
notificationTable.value.filters[0].value.includes(ref.type)
|
|
) {
|
|
uiStore.notifyMenu('notification');
|
|
}
|
|
database.addNotificationV2ToDatabase(ref);
|
|
notificationTable.value.data.push(ref);
|
|
queueNotificationNoty(ref);
|
|
sharedFeedStore.addEntry(ref);
|
|
}
|
|
|
|
function handleNotificationV2Update(args) {
|
|
const notificationId = args.params.notificationId;
|
|
const json = { ...args.json };
|
|
if (!json) {
|
|
return;
|
|
}
|
|
json.id = notificationId;
|
|
handleNotificationV2({
|
|
json,
|
|
params: {
|
|
notificationId
|
|
}
|
|
});
|
|
if (json.seen) {
|
|
handleNotificationSee(notificationId);
|
|
}
|
|
}
|
|
|
|
function handleNotificationV2Hide(notificationId) {
|
|
database.expireNotificationV2(notificationId);
|
|
const ref = notificationTable.value.data.find(
|
|
(n) => n.id === notificationId
|
|
);
|
|
if (ref) {
|
|
ref.expiresAt = new Date().toJSON();
|
|
ref.seen = true;
|
|
}
|
|
}
|
|
|
|
function expireFriendRequestNotifications() {
|
|
const array = notificationTable.value.data;
|
|
for (let i = array.length - 1; i >= 0; i--) {
|
|
if (
|
|
array[i].type === 'friendRequest' ||
|
|
array[i].type === 'ignoredFriendRequest'
|
|
) {
|
|
array.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} notificationId
|
|
*/
|
|
function expireNotification(notificationId) {
|
|
let ref;
|
|
const array = notificationTable.value.data;
|
|
for (let i = array.length - 1; i >= 0; i--) {
|
|
if (array[i].id === notificationId) {
|
|
ref = array[i];
|
|
break;
|
|
}
|
|
}
|
|
if (typeof ref === 'undefined') {
|
|
return;
|
|
}
|
|
ref.$isExpired = true;
|
|
database.updateNotificationExpired(ref);
|
|
handleNotificationExpire({
|
|
ref,
|
|
params: {
|
|
notificationId: ref.id
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async function refreshNotifications() {
|
|
isNotificationsLoading.value = true;
|
|
let count;
|
|
let params;
|
|
try {
|
|
expireFriendRequestNotifications();
|
|
params = {
|
|
n: 100,
|
|
offset: 0
|
|
};
|
|
count = 50; // 5000 max
|
|
for (let i = 0; i < count; i++) {
|
|
const args = await notificationRequest.getNotifications(params);
|
|
for (const json of args.json) {
|
|
handleNotification({
|
|
json,
|
|
params: {
|
|
notificationId: json.id
|
|
}
|
|
});
|
|
}
|
|
params.offset += 100;
|
|
if (args.json.length < 100) {
|
|
break;
|
|
}
|
|
}
|
|
params = {
|
|
n: 100,
|
|
offset: 0
|
|
};
|
|
count = 50; // 5000 max
|
|
for (let i = 0; i < count; i++) {
|
|
const args =
|
|
await notificationRequest.getNotificationsV2(params);
|
|
for (const json of args.json) {
|
|
handleNotificationV2({
|
|
json,
|
|
params: {
|
|
notificationId: json.id
|
|
}
|
|
});
|
|
}
|
|
params.offset += 100;
|
|
if (args.json.length < 100) {
|
|
break;
|
|
}
|
|
}
|
|
params = {
|
|
n: 100,
|
|
offset: 0
|
|
};
|
|
count = 50; // 5000 max
|
|
for (let i = 0; i < count; i++) {
|
|
const args =
|
|
await notificationRequest.getHiddenFriendRequests(params);
|
|
for (const json of args.json) {
|
|
json.type = 'ignoredFriendRequest';
|
|
handleNotification({
|
|
json,
|
|
params: {
|
|
notificationId: json.id
|
|
}
|
|
});
|
|
}
|
|
params.offset += 100;
|
|
if (args.json.length < 100) {
|
|
break;
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
} finally {
|
|
isNotificationsLoading.value = false;
|
|
notificationInitStatus.value = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {object} noty
|
|
*/
|
|
function queueNotificationNoty(noty) {
|
|
noty.isFriend = friendStore.friends.has(noty.senderUserId);
|
|
noty.isFavorite = friendStore.localFavoriteFriends.has(
|
|
noty.senderUserId
|
|
);
|
|
const notyFilter = notificationsSettingsStore.sharedFeedFilters.noty;
|
|
if (
|
|
notyFilter[noty.type] &&
|
|
(notyFilter[noty.type] === 'On' ||
|
|
notyFilter[noty.type] === 'Friends' ||
|
|
(notyFilter[noty.type] === 'VIP' && noty.isFavorite))
|
|
) {
|
|
playNoty(noty);
|
|
}
|
|
}
|
|
|
|
function playNoty(noty) {
|
|
if (
|
|
userStore.currentUser.status === 'busy' ||
|
|
!watchState.isFriendsLoaded
|
|
) {
|
|
return;
|
|
}
|
|
let displayName = '';
|
|
if (noty.displayName) {
|
|
displayName = noty.displayName;
|
|
} else if (noty.senderUsername) {
|
|
displayName = noty.senderUsername;
|
|
} else if (noty.sourceDisplayName) {
|
|
displayName = noty.sourceDisplayName;
|
|
}
|
|
if (displayName) {
|
|
// don't play noty twice
|
|
const notyId = `${noty.type},${displayName}`;
|
|
if (notyMap[notyId] && notyMap[notyId] >= noty.created_at) {
|
|
return;
|
|
}
|
|
notyMap[notyId] = noty.created_at;
|
|
}
|
|
const bias = new Date(Date.now() - 60000).toJSON();
|
|
for (const [notyId, createdAt] of Object.entries(notyMap)) {
|
|
if (createdAt < bias) {
|
|
delete notyMap[notyId];
|
|
}
|
|
}
|
|
if (noty.created_at < bias) {
|
|
// don't play noty if it's over 1min old
|
|
return;
|
|
}
|
|
|
|
const notiConditions = {
|
|
Always: () => true,
|
|
'Inside VR': () => gameStore.isSteamVRRunning,
|
|
'Outside VR': () => !gameStore.isSteamVRRunning,
|
|
'Game Closed': () => !gameStore.isGameRunning, // Also known as "Outside VRChat"
|
|
'Game Running': () => gameStore.isGameRunning, // Also known as "Inside VRChat"
|
|
'Desktop Mode': () =>
|
|
gameStore.isGameNoVR && gameStore.isGameRunning,
|
|
AFK: () =>
|
|
notificationsSettingsStore.afkDesktopToast &&
|
|
gameStore.isHmdAfk &&
|
|
gameStore.isGameRunning &&
|
|
!gameStore.isGameNoVR
|
|
};
|
|
|
|
const playNotificationTTS =
|
|
notiConditions[notificationsSettingsStore.notificationTTS]?.();
|
|
const playDesktopToast =
|
|
notiConditions[notificationsSettingsStore.desktopToast]?.() ||
|
|
notiConditions['AFK']();
|
|
|
|
const playOverlayToast =
|
|
notiConditions[notificationsSettingsStore.overlayToast]?.();
|
|
const playOverlayNotification =
|
|
notificationsSettingsStore.overlayNotifications && playOverlayToast;
|
|
const playXSNotification =
|
|
notificationsSettingsStore.xsNotifications && playOverlayToast;
|
|
const playOvrtHudNotifications =
|
|
notificationsSettingsStore.ovrtHudNotifications && playOverlayToast;
|
|
const playOvrtWristNotifications =
|
|
notificationsSettingsStore.ovrtWristNotifications &&
|
|
playOverlayToast;
|
|
|
|
let message = '';
|
|
if (noty.title) {
|
|
message = `${noty.title}, ${noty.message}`;
|
|
} else if (noty.message) {
|
|
message = noty.message;
|
|
}
|
|
const messageList = [
|
|
'inviteMessage',
|
|
'requestMessage',
|
|
'responseMessage'
|
|
];
|
|
for (let k = 0; k < messageList.length; k++) {
|
|
if (
|
|
typeof noty.details !== 'undefined' &&
|
|
typeof noty.details[messageList[k]] !== 'undefined'
|
|
) {
|
|
message = `, ${noty.details[messageList[k]]}`;
|
|
}
|
|
}
|
|
if (playNotificationTTS) {
|
|
playNotyTTS(noty, displayName, message);
|
|
}
|
|
if (
|
|
playDesktopToast ||
|
|
playXSNotification ||
|
|
playOvrtHudNotifications ||
|
|
playOvrtWristNotifications ||
|
|
playOverlayNotification
|
|
) {
|
|
if (notificationsSettingsStore.imageNotifications) {
|
|
notySaveImage(noty).then((image) => {
|
|
if (playXSNotification) {
|
|
displayXSNotification(noty, message, image);
|
|
}
|
|
if (
|
|
playOvrtHudNotifications ||
|
|
playOvrtWristNotifications
|
|
) {
|
|
displayOvrtNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
noty,
|
|
message,
|
|
image
|
|
);
|
|
}
|
|
if (playDesktopToast) {
|
|
displayDesktopToast(noty, message, image);
|
|
}
|
|
if (playOverlayNotification) {
|
|
displayOverlayNotification(noty, message, image);
|
|
}
|
|
});
|
|
} else {
|
|
if (playXSNotification) {
|
|
displayXSNotification(noty, message, '');
|
|
}
|
|
if (playOvrtHudNotifications || playOvrtWristNotifications) {
|
|
displayOvrtNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
noty,
|
|
message,
|
|
''
|
|
);
|
|
}
|
|
if (playDesktopToast) {
|
|
displayDesktopToast(noty, message, '');
|
|
}
|
|
if (playOverlayNotification) {
|
|
displayOverlayNotification(noty, message, '');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {object} noty
|
|
* @param {string} displayName
|
|
* @param {string} message
|
|
*/
|
|
async function playNotyTTS(noty, displayName, message) {
|
|
if (notificationsSettingsStore.notificationTTSNickName) {
|
|
const userId = getUserIdFromNoty(noty);
|
|
const memo = await getUserMemo(userId);
|
|
if (memo.memo) {
|
|
const array = memo.memo.split('\n');
|
|
const nickName = array[0];
|
|
displayName = nickName;
|
|
}
|
|
}
|
|
switch (noty.type) {
|
|
case 'OnPlayerJoined':
|
|
notificationsSettingsStore.speak(`${displayName} has joined`);
|
|
break;
|
|
case 'OnPlayerLeft':
|
|
notificationsSettingsStore.speak(`${displayName} has left`);
|
|
break;
|
|
case 'OnPlayerJoining':
|
|
notificationsSettingsStore.speak(`${displayName} is joining`);
|
|
break;
|
|
case 'GPS':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} is in ${displayLocation(
|
|
noty.location,
|
|
noty.worldName,
|
|
noty.groupName
|
|
)}`
|
|
);
|
|
break;
|
|
case 'Online':
|
|
let locationName = '';
|
|
if (noty.worldName) {
|
|
locationName = ` to ${displayLocation(
|
|
noty.location,
|
|
noty.worldName,
|
|
noty.groupName
|
|
)}`;
|
|
}
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} has logged in${locationName}`
|
|
);
|
|
break;
|
|
case 'Offline':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} has logged out`
|
|
);
|
|
break;
|
|
case 'Status':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} status is now ${noty.status} ${noty.statusDescription}`
|
|
);
|
|
break;
|
|
case 'invite':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} has invited you to ${displayLocation(
|
|
noty.details.worldId,
|
|
noty.details.worldName,
|
|
noty.groupName
|
|
)}${message}`
|
|
);
|
|
break;
|
|
case 'requestInvite':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} has requested an invite${message}`
|
|
);
|
|
break;
|
|
case 'inviteResponse':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} has responded to your invite${message}`
|
|
);
|
|
break;
|
|
case 'requestInviteResponse':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} has responded to your invite request${message}`
|
|
);
|
|
break;
|
|
case 'friendRequest':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} has sent you a friend request`
|
|
);
|
|
break;
|
|
case 'Friend':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} is now your friend`
|
|
);
|
|
break;
|
|
case 'Unfriend':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} is no longer your friend`
|
|
);
|
|
break;
|
|
case 'TrustLevel':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} trust level is now ${noty.trustLevel}`
|
|
);
|
|
break;
|
|
case 'DisplayName':
|
|
notificationsSettingsStore.speak(
|
|
`${noty.previousDisplayName} changed their name to ${noty.displayName}`
|
|
);
|
|
break;
|
|
case 'boop':
|
|
notificationsSettingsStore.speak(noty.message);
|
|
break;
|
|
case 'groupChange':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} ${noty.message}`
|
|
);
|
|
break;
|
|
case 'group.announcement':
|
|
notificationsSettingsStore.speak(noty.message);
|
|
break;
|
|
case 'group.informative':
|
|
notificationsSettingsStore.speak(noty.message);
|
|
break;
|
|
case 'group.invite':
|
|
notificationsSettingsStore.speak(noty.message);
|
|
break;
|
|
case 'group.joinRequest':
|
|
notificationsSettingsStore.speak(noty.message);
|
|
break;
|
|
case 'group.transfer':
|
|
notificationsSettingsStore.speak(noty.message);
|
|
break;
|
|
case 'group.queueReady':
|
|
notificationsSettingsStore.speak(noty.message);
|
|
break;
|
|
case 'instance.closed':
|
|
notificationsSettingsStore.speak(noty.message);
|
|
break;
|
|
case 'PortalSpawn':
|
|
if (displayName) {
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} has spawned a portal to ${displayLocation(
|
|
noty.instanceId,
|
|
noty.worldName,
|
|
noty.groupName
|
|
)}`
|
|
);
|
|
} else {
|
|
notificationsSettingsStore.speak(
|
|
'User has spawned a portal'
|
|
);
|
|
}
|
|
break;
|
|
case 'AvatarChange':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} changed into avatar ${noty.name}`
|
|
);
|
|
break;
|
|
case 'ChatBoxMessage':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} said ${noty.text}`
|
|
);
|
|
break;
|
|
case 'Event':
|
|
notificationsSettingsStore.speak(noty.data);
|
|
break;
|
|
case 'External':
|
|
notificationsSettingsStore.speak(noty.message);
|
|
break;
|
|
case 'VideoPlay':
|
|
notificationsSettingsStore.speak(
|
|
`Now playing: ${noty.notyName}`
|
|
);
|
|
break;
|
|
case 'BlockedOnPlayerJoined':
|
|
notificationsSettingsStore.speak(
|
|
`Blocked user ${displayName} has joined`
|
|
);
|
|
break;
|
|
case 'BlockedOnPlayerLeft':
|
|
notificationsSettingsStore.speak(
|
|
`Blocked user ${displayName} has left`
|
|
);
|
|
break;
|
|
case 'MutedOnPlayerJoined':
|
|
notificationsSettingsStore.speak(
|
|
`Muted user ${displayName} has joined`
|
|
);
|
|
break;
|
|
case 'MutedOnPlayerLeft':
|
|
notificationsSettingsStore.speak(
|
|
`Muted user ${displayName} has left`
|
|
);
|
|
break;
|
|
case 'Blocked':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} has blocked you`
|
|
);
|
|
break;
|
|
case 'Unblocked':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} has unblocked you`
|
|
);
|
|
break;
|
|
case 'Muted':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} has muted you`
|
|
);
|
|
break;
|
|
case 'Unmuted':
|
|
notificationsSettingsStore.speak(
|
|
`${displayName} has unmuted you`
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {object} noty
|
|
* @returns
|
|
*/
|
|
async function notySaveImage(noty) {
|
|
const imageUrl = await notyGetImage(noty);
|
|
let fileId = extractFileId(imageUrl);
|
|
let fileVersion = extractFileVersion(imageUrl);
|
|
let imageLocation = '';
|
|
try {
|
|
if (fileId && fileVersion) {
|
|
imageLocation = await AppApi.GetImage(
|
|
imageUrl,
|
|
fileId,
|
|
fileVersion
|
|
);
|
|
} else if (imageUrl && imageUrl.startsWith('http')) {
|
|
fileVersion = imageUrl.split('/').pop(); // 1416226261.thumbnail-500.png
|
|
fileId = fileVersion.split('.').shift(); // 1416226261
|
|
imageLocation = await AppApi.GetImage(
|
|
imageUrl,
|
|
fileId,
|
|
fileVersion
|
|
);
|
|
}
|
|
} catch (err) {
|
|
console.error(imageUrl, err);
|
|
}
|
|
return imageLocation;
|
|
}
|
|
|
|
function displayDesktopToast(noty, message, image) {
|
|
switch (noty.type) {
|
|
case 'OnPlayerJoined':
|
|
desktopNotification(noty.displayName, 'has joined', image);
|
|
break;
|
|
case 'OnPlayerLeft':
|
|
desktopNotification(noty.displayName, 'has left', image);
|
|
break;
|
|
case 'OnPlayerJoining':
|
|
desktopNotification(noty.displayName, 'is joining', image);
|
|
break;
|
|
case 'GPS':
|
|
desktopNotification(
|
|
noty.displayName,
|
|
`is in ${displayLocation(
|
|
noty.location,
|
|
noty.worldName,
|
|
noty.groupName
|
|
)}`,
|
|
image
|
|
);
|
|
break;
|
|
case 'Online':
|
|
let locationName = '';
|
|
if (noty.worldName) {
|
|
locationName = ` to ${displayLocation(
|
|
noty.location,
|
|
noty.worldName,
|
|
noty.groupName
|
|
)}`;
|
|
}
|
|
desktopNotification(
|
|
noty.displayName,
|
|
`has logged in${locationName}`,
|
|
image
|
|
);
|
|
break;
|
|
case 'Offline':
|
|
desktopNotification(noty.displayName, 'has logged out', image);
|
|
break;
|
|
case 'Status':
|
|
desktopNotification(
|
|
noty.displayName,
|
|
`status is now ${noty.status} ${noty.statusDescription}`,
|
|
image
|
|
);
|
|
break;
|
|
case 'invite':
|
|
desktopNotification(
|
|
noty.senderUsername,
|
|
`has invited you to ${displayLocation(
|
|
noty.details.worldId,
|
|
noty.details.worldName
|
|
)}${message}`,
|
|
image
|
|
);
|
|
break;
|
|
case 'requestInvite':
|
|
desktopNotification(
|
|
noty.senderUsername,
|
|
`has requested an invite${message}`,
|
|
image
|
|
);
|
|
break;
|
|
case 'inviteResponse':
|
|
desktopNotification(
|
|
noty.senderUsername,
|
|
`has responded to your invite${message}`,
|
|
image
|
|
);
|
|
break;
|
|
case 'requestInviteResponse':
|
|
desktopNotification(
|
|
noty.senderUsername,
|
|
`has responded to your invite request${message}`,
|
|
image
|
|
);
|
|
break;
|
|
case 'friendRequest':
|
|
desktopNotification(
|
|
noty.senderUsername,
|
|
'has sent you a friend request',
|
|
image
|
|
);
|
|
break;
|
|
case 'Friend':
|
|
desktopNotification(
|
|
noty.displayName,
|
|
'is now your friend',
|
|
image
|
|
);
|
|
break;
|
|
case 'Unfriend':
|
|
desktopNotification(
|
|
noty.displayName,
|
|
'is no longer your friend',
|
|
image
|
|
);
|
|
break;
|
|
case 'TrustLevel':
|
|
desktopNotification(
|
|
noty.displayName,
|
|
`trust level is now ${noty.trustLevel}`,
|
|
image
|
|
);
|
|
break;
|
|
case 'DisplayName':
|
|
desktopNotification(
|
|
noty.previousDisplayName,
|
|
`changed their name to ${noty.displayName}`,
|
|
image
|
|
);
|
|
break;
|
|
case 'boop':
|
|
desktopNotification(noty.senderUsername, noty.message, image);
|
|
break;
|
|
case 'groupChange':
|
|
desktopNotification(noty.senderUsername, noty.message, image);
|
|
break;
|
|
case 'group.announcement':
|
|
desktopNotification('Group Announcement', noty.message, image);
|
|
break;
|
|
case 'group.informative':
|
|
desktopNotification('Group Informative', noty.message, image);
|
|
break;
|
|
case 'group.invite':
|
|
desktopNotification('Group Invite', noty.message, image);
|
|
break;
|
|
case 'group.joinRequest':
|
|
desktopNotification('Group Join Request', noty.message, image);
|
|
break;
|
|
case 'group.transfer':
|
|
desktopNotification(
|
|
'Group Transfer Request',
|
|
noty.message,
|
|
image
|
|
);
|
|
break;
|
|
case 'group.queueReady':
|
|
desktopNotification(
|
|
'Instance Queue Ready',
|
|
noty.message,
|
|
image
|
|
);
|
|
break;
|
|
case 'instance.closed':
|
|
desktopNotification('Instance Closed', noty.message, image);
|
|
break;
|
|
case 'PortalSpawn':
|
|
if (noty.displayName) {
|
|
desktopNotification(
|
|
noty.displayName,
|
|
`has spawned a portal to ${displayLocation(
|
|
noty.instanceId,
|
|
noty.worldName,
|
|
noty.groupName
|
|
)}`,
|
|
image
|
|
);
|
|
} else {
|
|
desktopNotification('', 'User has spawned a portal', image);
|
|
}
|
|
break;
|
|
case 'AvatarChange':
|
|
desktopNotification(
|
|
noty.displayName,
|
|
`changed into avatar ${noty.name}`,
|
|
image
|
|
);
|
|
break;
|
|
case 'ChatBoxMessage':
|
|
desktopNotification(
|
|
noty.displayName,
|
|
`said ${noty.text}`,
|
|
image
|
|
);
|
|
break;
|
|
case 'Event':
|
|
desktopNotification('Event', noty.data, image);
|
|
break;
|
|
case 'External':
|
|
desktopNotification('External', noty.message, image);
|
|
break;
|
|
case 'VideoPlay':
|
|
desktopNotification('Now playing', noty.notyName, image);
|
|
break;
|
|
case 'BlockedOnPlayerJoined':
|
|
desktopNotification(
|
|
noty.displayName,
|
|
'blocked user has joined',
|
|
image
|
|
);
|
|
break;
|
|
case 'BlockedOnPlayerLeft':
|
|
desktopNotification(
|
|
noty.displayName,
|
|
'blocked user has left',
|
|
image
|
|
);
|
|
break;
|
|
case 'MutedOnPlayerJoined':
|
|
desktopNotification(
|
|
noty.displayName,
|
|
'muted user has joined',
|
|
image
|
|
);
|
|
break;
|
|
case 'MutedOnPlayerLeft':
|
|
desktopNotification(
|
|
noty.displayName,
|
|
'muted user has left',
|
|
image
|
|
);
|
|
break;
|
|
case 'Blocked':
|
|
desktopNotification(noty.displayName, 'has blocked you', image);
|
|
break;
|
|
case 'Unblocked':
|
|
desktopNotification(
|
|
noty.displayName,
|
|
'has unblocked you',
|
|
image
|
|
);
|
|
break;
|
|
case 'Muted':
|
|
desktopNotification(noty.displayName, 'has muted you', image);
|
|
break;
|
|
case 'Unmuted':
|
|
desktopNotification(noty.displayName, 'has unmuted you', image);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} noty
|
|
* @param {string} message
|
|
* @param {string} imageFile
|
|
*/
|
|
function displayOverlayNotification(noty, message, imageFile) {
|
|
let image = '';
|
|
if (imageFile) {
|
|
image = `file:///${imageFile}`;
|
|
}
|
|
AppApi.ExecuteVrOverlayFunction(
|
|
'playNoty',
|
|
JSON.stringify({ noty, message, image })
|
|
);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {any} noty
|
|
* @param {string} message
|
|
* @param {string} image
|
|
*/
|
|
function displayXSNotification(noty, message, image) {
|
|
const timeout = Math.floor(
|
|
parseInt(
|
|
notificationsSettingsStore.notificationTimeout.toString(),
|
|
10
|
|
) / 1000
|
|
);
|
|
const opacity =
|
|
parseFloat(advancedSettingsStore.notificationOpacity.toString()) /
|
|
100;
|
|
switch (noty.type) {
|
|
case 'OnPlayerJoined':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} has joined`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'OnPlayerLeft':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} has left`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'OnPlayerJoining':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} is joining`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'GPS':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} is in ${displayLocation(
|
|
noty.location,
|
|
noty.worldName,
|
|
noty.groupName
|
|
)}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Online':
|
|
let locationName = '';
|
|
if (noty.worldName) {
|
|
locationName = ` to ${displayLocation(
|
|
noty.location,
|
|
noty.worldName,
|
|
noty.groupName
|
|
)}`;
|
|
}
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} has logged in${locationName}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Offline':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} has logged out`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Status':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} status is now ${noty.status} ${noty.statusDescription}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'invite':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${
|
|
noty.senderUsername
|
|
} has invited you to ${displayLocation(
|
|
noty.details.worldId,
|
|
noty.details.worldName
|
|
)}${message}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'requestInvite':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.senderUsername} has requested an invite${message}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'inviteResponse':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.senderUsername} has responded to your invite${message}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'requestInviteResponse':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.senderUsername} has responded to your invite request${message}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'friendRequest':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.senderUsername} has sent you a friend request`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Friend':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} is now your friend`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Unfriend':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} is no longer your friend`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'TrustLevel':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} trust level is now ${noty.trustLevel}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'DisplayName':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.previousDisplayName} changed their name to ${noty.displayName}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'boop':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'groupChange':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.senderUsername}: ${noty.message}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'group.announcement':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'group.informative':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'group.invite':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'group.joinRequest':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'group.transfer':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'group.queueReady':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'instance.closed':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'PortalSpawn':
|
|
if (noty.displayName) {
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${
|
|
noty.displayName
|
|
} has spawned a portal to ${displayLocation(
|
|
noty.instanceId,
|
|
noty.worldName,
|
|
noty.groupName
|
|
)}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
} else {
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
'User has spawned a portal',
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
}
|
|
break;
|
|
case 'AvatarChange':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} changed into avatar ${noty.name}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'ChatBoxMessage':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} said ${noty.text}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Event':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
noty.data,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'External':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'VideoPlay':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`Now playing: ${noty.notyName}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'BlockedOnPlayerJoined':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`Blocked user ${noty.displayName} has joined`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'BlockedOnPlayerLeft':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`Blocked user ${noty.displayName} has left`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'MutedOnPlayerJoined':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`Muted user ${noty.displayName} has joined`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'MutedOnPlayerLeft':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`Muted user ${noty.displayName} has left`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Blocked':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} has blocked you`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Unblocked':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} has unblocked you`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Muted':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} has muted you`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Unmuted':
|
|
AppApi.XSNotification(
|
|
'VRCX',
|
|
`${noty.displayName} has unmuted you`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
function displayOvrtNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
noty,
|
|
message,
|
|
image
|
|
) {
|
|
const timeout = Math.floor(
|
|
parseInt(
|
|
notificationsSettingsStore.notificationTimeout.toString(),
|
|
10
|
|
) / 1000
|
|
);
|
|
const opacity =
|
|
parseFloat(advancedSettingsStore.notificationOpacity.toString()) /
|
|
100;
|
|
switch (noty.type) {
|
|
case 'OnPlayerJoined':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} has joined`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'OnPlayerLeft':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} has left`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'OnPlayerJoining':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} is joining`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'GPS':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} is in ${displayLocation(
|
|
noty.location,
|
|
noty.worldName,
|
|
noty.groupName
|
|
)}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Online':
|
|
let locationName = '';
|
|
if (noty.worldName) {
|
|
locationName = ` to ${displayLocation(
|
|
noty.location,
|
|
noty.worldName,
|
|
noty.groupName
|
|
)}`;
|
|
}
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} has logged in${locationName}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Offline':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} has logged out`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Status':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} status is now ${noty.status} ${noty.statusDescription}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'invite':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${
|
|
noty.senderUsername
|
|
} has invited you to ${displayLocation(
|
|
noty.details.worldId,
|
|
noty.details.worldName
|
|
)}${message}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'requestInvite':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.senderUsername} has requested an invite${message}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'inviteResponse':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.senderUsername} has responded to your invite${message}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'requestInviteResponse':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.senderUsername} has responded to your invite request${message}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'friendRequest':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.senderUsername} has sent you a friend request`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Friend':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} is now your friend`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Unfriend':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} is no longer your friend`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'TrustLevel':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} trust level is now ${noty.trustLevel}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'DisplayName':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.previousDisplayName} changed their name to ${noty.displayName}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'boop':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'groupChange':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.senderUsername}: ${noty.message}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'group.announcement':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'group.informative':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'group.invite':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'group.joinRequest':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'group.transfer':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'group.queueReady':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'instance.closed':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'PortalSpawn':
|
|
if (noty.displayName) {
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${
|
|
noty.displayName
|
|
} has spawned a portal to ${displayLocation(
|
|
noty.instanceId,
|
|
noty.worldName,
|
|
noty.groupName
|
|
)}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
} else {
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
'User has spawned a portal',
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
}
|
|
break;
|
|
case 'AvatarChange':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} changed into avatar ${noty.name}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'ChatBoxMessage':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} said ${noty.text}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Event':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
noty.data,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'External':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
noty.message,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'VideoPlay':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`Now playing: ${noty.notyName}`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'BlockedOnPlayerJoined':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`Blocked user ${noty.displayName} has joined`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'BlockedOnPlayerLeft':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`Blocked user ${noty.displayName} has left`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'MutedOnPlayerJoined':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`Muted user ${noty.displayName} has joined`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'MutedOnPlayerLeft':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`Muted user ${noty.displayName} has left`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Blocked':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} has blocked you`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Unblocked':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} has unblocked you`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Muted':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} has muted you`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
case 'Unmuted':
|
|
AppApi.OVRTNotification(
|
|
playOvrtHudNotifications,
|
|
playOvrtWristNotifications,
|
|
'VRCX',
|
|
`${noty.displayName} has unmuted you`,
|
|
timeout,
|
|
opacity,
|
|
image
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {object} noty
|
|
* @returns
|
|
*/
|
|
function getUserIdFromNoty(noty) {
|
|
let userId = '';
|
|
if (noty.userId) {
|
|
userId = noty.userId;
|
|
} else if (noty.senderUserId) {
|
|
userId = noty.senderUserId;
|
|
} else if (noty.sourceUserId) {
|
|
userId = noty.sourceUserId;
|
|
} else if (noty.displayName) {
|
|
for (const ref of userStore.cachedUsers.values()) {
|
|
if (ref.displayName === noty.displayName) {
|
|
userId = ref.id;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return userId;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {object} noty
|
|
* @returns
|
|
*/
|
|
async function notyGetImage(noty) {
|
|
let imageUrl = '';
|
|
const userId = getUserIdFromNoty(noty);
|
|
|
|
if (noty.thumbnailImageUrl) {
|
|
imageUrl = noty.thumbnailImageUrl;
|
|
} else if (noty.details && noty.details.imageUrl) {
|
|
imageUrl = noty.details.imageUrl;
|
|
} else if (noty.imageUrl) {
|
|
imageUrl = noty.imageUrl;
|
|
} else if (userId && !userId.startsWith('grp_')) {
|
|
imageUrl = await userRequest
|
|
.getCachedUser({
|
|
userId
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
return '';
|
|
})
|
|
.then((args) => {
|
|
if (!args.json) {
|
|
return '';
|
|
}
|
|
if (
|
|
appearanceSettingsStore.displayVRCPlusIconsAsAvatar &&
|
|
args.json.userIcon
|
|
) {
|
|
return args.json.userIcon;
|
|
}
|
|
if (args.json.profilePicOverride) {
|
|
return args.json.profilePicOverride;
|
|
}
|
|
return args.json.currentAvatarThumbnailImageUrl;
|
|
});
|
|
}
|
|
return imageUrl;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} displayName
|
|
* @param {string} message
|
|
* @param {string} image
|
|
*/
|
|
function desktopNotification(displayName, message, image) {
|
|
if (WINDOWS) {
|
|
AppApi.DesktopNotification(displayName, message, image);
|
|
} else {
|
|
window.electron.desktopNotification(displayName, message, image);
|
|
}
|
|
}
|
|
|
|
function queueGameLogNoty(gamelog) {
|
|
const noty = structuredClone(gamelog);
|
|
let bias;
|
|
// remove join/leave notifications when switching worlds
|
|
if (
|
|
noty.type === 'OnPlayerJoined'
|
|
// noty.type === 'BlockedOnPlayerJoined' ||
|
|
// noty.type === 'MutedOnPlayerJoined'
|
|
) {
|
|
bias = locationStore.lastLocation.date + 30 * 1000; // 30 secs
|
|
if (Date.parse(noty.created_at) <= bias) {
|
|
return;
|
|
}
|
|
}
|
|
if (
|
|
noty.type === 'OnPlayerLeft' ||
|
|
noty.type === 'BlockedOnPlayerLeft' ||
|
|
noty.type === 'MutedOnPlayerLeft'
|
|
) {
|
|
bias = locationStore.lastLocationDestinationTime + 5 * 1000; // 5 secs
|
|
if (Date.parse(noty.created_at) <= bias) {
|
|
return;
|
|
}
|
|
}
|
|
if (
|
|
noty.type === 'Notification' ||
|
|
noty.type === 'LocationDestination'
|
|
// skip unused entries
|
|
) {
|
|
return;
|
|
}
|
|
if (noty.type === 'VideoPlay') {
|
|
if (!noty.videoName) {
|
|
// skip video without name
|
|
return;
|
|
}
|
|
noty.notyName = noty.videoName;
|
|
if (noty.displayName) {
|
|
// add requester's name to noty
|
|
noty.notyName = `${noty.videoName} (${noty.displayName})`;
|
|
}
|
|
}
|
|
if (
|
|
noty.type !== 'VideoPlay' &&
|
|
noty.displayName === userStore.currentUser.displayName
|
|
) {
|
|
// remove current user
|
|
return;
|
|
}
|
|
noty.isFriend = false;
|
|
noty.isFavorite = false;
|
|
if (noty.userId) {
|
|
noty.isFriend = friendStore.friends.has(noty.userId);
|
|
noty.isFavorite = friendStore.localFavoriteFriends.has(noty.userId);
|
|
} else if (noty.displayName) {
|
|
for (const ref of userStore.cachedUsers.values()) {
|
|
if (ref.displayName === noty.displayName) {
|
|
noty.isFriend = friendStore.friends.has(ref.id);
|
|
noty.isFavorite = friendStore.localFavoriteFriends.has(
|
|
ref.id
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
const notyFilter = notificationsSettingsStore.sharedFeedFilters.noty;
|
|
if (
|
|
notyFilter[noty.type] &&
|
|
(notyFilter[noty.type] === 'On' ||
|
|
notyFilter[noty.type] === 'Everyone' ||
|
|
(notyFilter[noty.type] === 'Friends' && noty.isFriend) ||
|
|
(notyFilter[noty.type] === 'VIP' && noty.isFavorite))
|
|
) {
|
|
playNoty(noty);
|
|
}
|
|
}
|
|
|
|
function queueFeedNoty(feed) {
|
|
const noty = { ...feed };
|
|
if (noty.type === 'Avatar') {
|
|
return;
|
|
}
|
|
// hide private worlds from feed
|
|
if (
|
|
wristOverlaySettingsStore.hidePrivateFromFeed &&
|
|
noty.type === 'GPS' &&
|
|
noty.location === 'private'
|
|
) {
|
|
return;
|
|
}
|
|
noty.isFriend = friendStore.friends.has(noty.userId);
|
|
noty.isFavorite = friendStore.localFavoriteFriends.has(noty.userId);
|
|
const notyFilter = notificationsSettingsStore.sharedFeedFilters.noty;
|
|
if (
|
|
notyFilter[noty.type] &&
|
|
(notyFilter[noty.type] === 'Everyone' ||
|
|
(notyFilter[noty.type] === 'Friends' && noty.isFriend) ||
|
|
(notyFilter[noty.type] === 'VIP' && noty.isFavorite))
|
|
) {
|
|
playNoty(noty);
|
|
}
|
|
}
|
|
|
|
function queueFriendLogNoty(noty) {
|
|
if (noty.type === 'FriendRequest') {
|
|
return;
|
|
}
|
|
noty.isFriend = friendStore.friends.has(noty.userId);
|
|
noty.isFavorite = friendStore.localFavoriteFriends.has(noty.userId);
|
|
const notyFilter = notificationsSettingsStore.sharedFeedFilters.noty;
|
|
if (
|
|
notyFilter[noty.type] &&
|
|
(notyFilter[noty.type] === 'On' ||
|
|
notyFilter[noty.type] === 'Friends' ||
|
|
(notyFilter[noty.type] === 'VIP' && noty.isFavorite))
|
|
) {
|
|
playNoty(noty);
|
|
}
|
|
}
|
|
|
|
function queueModerationNoty(noty) {
|
|
noty.isFriend = false;
|
|
noty.isFavorite = false;
|
|
if (noty.userId) {
|
|
noty.isFriend = friendStore.friends.has(noty.userId);
|
|
noty.isFavorite = friendStore.localFavoriteFriends.has(noty.userId);
|
|
}
|
|
const notyFilter = notificationsSettingsStore.sharedFeedFilters.noty;
|
|
if (notyFilter[noty.type] && notyFilter[noty.type] === 'On') {
|
|
playNoty(noty);
|
|
}
|
|
}
|
|
|
|
async function initNotifications() {
|
|
notificationInitStatus.value = false;
|
|
let tableData = await database.getNotificationsV2();
|
|
let notifications = await database.getNotifications();
|
|
tableData = tableData.concat(
|
|
notifications.filter((n) => !tableData.some((t) => t.id === n.id))
|
|
);
|
|
tableData.sort(
|
|
(a, b) => Date.parse(b.created_at) - Date.parse(a.created_at)
|
|
);
|
|
tableData.splice(dbVars.maxTableSize);
|
|
notificationTable.value.data = tableData;
|
|
refreshNotifications();
|
|
}
|
|
|
|
function testNotification() {
|
|
playNoty({
|
|
type: 'Event',
|
|
created_at: new Date().toJSON(),
|
|
data: 'Notification Test'
|
|
});
|
|
}
|
|
|
|
function acceptFriendRequestNotification(row) {
|
|
modalStore
|
|
.confirm({
|
|
description: t('confirm.accept_friend_request'),
|
|
title: t('confirm.title')
|
|
})
|
|
.then(({ ok }) => {
|
|
if (!ok) return;
|
|
notificationRequest.acceptFriendRequestNotification({
|
|
notificationId: row.id
|
|
});
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
async function hideNotification(row) {
|
|
if (row.type === 'ignoredFriendRequest') {
|
|
await friendRequest.deleteHiddenFriendRequest(
|
|
{ notificationId: row.id },
|
|
row.senderUserId
|
|
);
|
|
handleNotificationHide(row.id);
|
|
} else {
|
|
notificationRequest.hideNotification({
|
|
notificationId: row.id
|
|
});
|
|
}
|
|
}
|
|
|
|
function hideNotificationPrompt(row) {
|
|
modalStore
|
|
.confirm({
|
|
description: t('confirm.decline_type', { type: row.type }),
|
|
title: t('confirm.title')
|
|
})
|
|
.then(({ ok }) => {
|
|
if (ok) hideNotification(row);
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
function acceptRequestInvite(row) {
|
|
modalStore
|
|
.confirm({
|
|
description: t('confirm.send_invite'),
|
|
title: t('confirm.title')
|
|
})
|
|
.then(({ ok }) => {
|
|
if (!ok) return;
|
|
let currentLocation = locationStore.lastLocation.location;
|
|
if (locationStore.lastLocation.location === 'traveling') {
|
|
currentLocation = locationStore.lastLocationDestination;
|
|
}
|
|
if (!currentLocation) {
|
|
currentLocation = userStore.currentUser?.$locationTag;
|
|
}
|
|
const L = parseLocation(currentLocation);
|
|
worldRequest
|
|
.getCachedWorld({ worldId: L.worldId })
|
|
.then((args) => {
|
|
notificationRequest
|
|
.sendInvite(
|
|
{
|
|
instanceId: L.tag,
|
|
worldId: L.tag,
|
|
worldName: args.ref.name,
|
|
rsvp: true
|
|
},
|
|
row.senderUserId
|
|
)
|
|
.then((_args) => {
|
|
toast(t('message.invite.sent'));
|
|
notificationRequest.hideNotification({
|
|
notificationId: row.id
|
|
});
|
|
return _args;
|
|
});
|
|
});
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
function sendNotificationResponse(notificationId, responses, responseType) {
|
|
if (!Array.isArray(responses) || responses.length === 0) return;
|
|
let responseData = '';
|
|
for (let i = 0; i < responses.length; i++) {
|
|
if (responses[i].type === responseType) {
|
|
responseData = responses[i].data;
|
|
break;
|
|
}
|
|
}
|
|
const params = { notificationId, responseType, responseData };
|
|
notificationRequest.sendNotificationResponse(params).then((args) => {
|
|
console.log('Notification response', args);
|
|
if (!args.json) return;
|
|
handleNotificationV2Hide(notificationId);
|
|
new Noty({
|
|
type: 'success',
|
|
text: escapeTag(args.json)
|
|
}).show();
|
|
});
|
|
}
|
|
|
|
function deleteNotificationLog(row) {
|
|
const idx = notificationTable.value.data.findIndex(
|
|
(e) => e.id === row.id
|
|
);
|
|
if (idx !== -1) {
|
|
notificationTable.value.data.splice(idx, 1);
|
|
}
|
|
if (
|
|
row.type !== 'friendRequest' &&
|
|
row.type !== 'ignoredFriendRequest'
|
|
) {
|
|
if (!row.version || row.version < 2) {
|
|
database.deleteNotification(row.id);
|
|
} else {
|
|
database.deleteNotificationV2(row.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
function deleteNotificationLogPrompt(row) {
|
|
modalStore
|
|
.confirm({
|
|
description: t('confirm.delete_type', { type: row.type }),
|
|
title: t('confirm.title')
|
|
})
|
|
.then(({ ok }) => {
|
|
if (ok) deleteNotificationLog(row);
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
function isNotificationExpired(notification) {
|
|
if (notification.$isExpired !== undefined) {
|
|
return notification.$isExpired;
|
|
}
|
|
if (!notification.expiresAt) {
|
|
return false;
|
|
}
|
|
const expiresAt = dayjs(notification.expiresAt);
|
|
return expiresAt.isValid() && dayjs().isSameOrAfter(expiresAt);
|
|
}
|
|
|
|
function openNotificationLink(link) {
|
|
if (!link) {
|
|
return;
|
|
}
|
|
const data = link.split(':');
|
|
if (!data.length) {
|
|
return;
|
|
}
|
|
switch (data[0]) {
|
|
case 'group':
|
|
groupStore.showGroupDialog(data[1]);
|
|
break;
|
|
case 'user':
|
|
userStore.showUserDialog(data[1]);
|
|
break;
|
|
case 'event':
|
|
const ids = data[1].split(',');
|
|
if (ids.length < 2) {
|
|
console.error('Invalid event notification link:', data[1]);
|
|
return;
|
|
}
|
|
|
|
groupStore.showGroupDialog(ids[0]);
|
|
// ids[1] cal_ is the event id
|
|
break;
|
|
case 'openNotificationLink':
|
|
default:
|
|
toast.error('Unsupported notification link type');
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
notificationInitStatus,
|
|
notificationTable,
|
|
unseenNotifications,
|
|
isNotificationsLoading,
|
|
|
|
initNotifications,
|
|
expireNotification,
|
|
refreshNotifications,
|
|
queueNotificationNoty,
|
|
playNoty,
|
|
queueGameLogNoty,
|
|
queueFeedNoty,
|
|
queueFriendLogNoty,
|
|
queueModerationNoty,
|
|
handleNotificationAccept,
|
|
handleNotificationSee,
|
|
handlePipelineNotification,
|
|
handleNotificationV2Update,
|
|
handleNotificationHide,
|
|
handleNotificationV2Hide,
|
|
handleNotification,
|
|
handleNotificationV2,
|
|
testNotification,
|
|
|
|
// Notification actions
|
|
acceptFriendRequestNotification,
|
|
hideNotification,
|
|
hideNotificationPrompt,
|
|
acceptRequestInvite,
|
|
sendNotificationResponse,
|
|
deleteNotificationLog,
|
|
deleteNotificationLogPrompt,
|
|
|
|
isNotificationCenterOpen,
|
|
friendNotifications,
|
|
groupNotifications,
|
|
otherNotifications,
|
|
unseenFriendNotifications,
|
|
unseenGroupNotifications,
|
|
unseenOtherNotifications,
|
|
hasUnseenNotifications,
|
|
getNotificationCategory,
|
|
isNotificationExpired,
|
|
openNotificationLink
|
|
};
|
|
});
|