mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-13 20:03:51 +02:00
2363 lines
77 KiB
JavaScript
2363 lines
77 KiB
JavaScript
import { ref, watch } from 'vue';
|
|
import { defineStore } from 'pinia';
|
|
|
|
import Noty from 'noty';
|
|
|
|
import {
|
|
checkCanInvite,
|
|
displayLocation,
|
|
extractFileId,
|
|
extractFileVersion,
|
|
getUserMemo,
|
|
parseLocation,
|
|
removeFromArray,
|
|
replaceBioSymbols
|
|
} from '../shared/utils';
|
|
import {
|
|
instanceRequest,
|
|
notificationRequest,
|
|
userRequest,
|
|
worldRequest
|
|
} from '../api';
|
|
import { AppDebug } from '../service/appConfig';
|
|
import { database } from '../service/database';
|
|
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 { useInstanceStore } from './instance';
|
|
import { useLocationStore } from './location';
|
|
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 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 notificationInitStatus = ref(false);
|
|
const notificationTable = ref({
|
|
data: [],
|
|
filters: [
|
|
{
|
|
prop: 'type',
|
|
value: []
|
|
},
|
|
{
|
|
prop: ['senderUsername', 'message'],
|
|
value: ''
|
|
}
|
|
],
|
|
tableProps: {
|
|
stripe: true,
|
|
size: 'small',
|
|
defaultSort: {
|
|
prop: 'created_at',
|
|
order: 'descending'
|
|
}
|
|
},
|
|
pageSize: 15,
|
|
pageSizeLinked: true,
|
|
paginationProps: {
|
|
small: true,
|
|
layout: 'sizes,prev,pager,next,total',
|
|
pageSizes: [10, 15, 20, 25, 50, 100]
|
|
}
|
|
});
|
|
const unseenNotifications = ref([]);
|
|
const isNotificationsLoading = ref(false);
|
|
|
|
const notyMap = ref([]);
|
|
|
|
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;
|
|
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.getInstance({
|
|
worldId: L.worldId,
|
|
instanceId: L.tag
|
|
});
|
|
}
|
|
}
|
|
if (
|
|
notificationTable.value.filters[0].value.length === 0 ||
|
|
notificationTable.value.filters[0].value.includes(ref.type)
|
|
) {
|
|
uiStore.notifyMenu('notification');
|
|
}
|
|
unseenNotifications.value.push(ref.id);
|
|
queueNotificationNoty(ref);
|
|
}
|
|
}
|
|
notificationTable.value.data.push(ref);
|
|
sharedFeedStore.updateSharedFeed(true);
|
|
const D = userStore.userDialog;
|
|
if (
|
|
D.visible === false ||
|
|
ref.$isDeleted ||
|
|
ref.type !== 'friendRequest' ||
|
|
ref.senderUserId !== D.id
|
|
) {
|
|
return;
|
|
}
|
|
D.incomingRequest = true;
|
|
}
|
|
|
|
function handleNotificationHide(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;
|
|
}
|
|
args.ref = ref;
|
|
if (
|
|
ref.type === 'friendRequest' ||
|
|
ref.type === 'ignoredFriendRequest' ||
|
|
ref.type.includes('.')
|
|
) {
|
|
for (let i = array.length - 1; i >= 0; i--) {
|
|
if (array[i].id === ref.id) {
|
|
array.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
ref.$isExpired = true;
|
|
database.updateNotificationExpired(ref);
|
|
}
|
|
handleNotificationExpire({
|
|
ref,
|
|
params: {
|
|
notificationId: ref.id
|
|
}
|
|
});
|
|
}
|
|
|
|
function handleNotificationV2Update(args) {
|
|
const notificationId = args.params.notificationId;
|
|
const json = args.json;
|
|
if (!json) {
|
|
return;
|
|
}
|
|
json.id = notificationId;
|
|
handleNotification({
|
|
json,
|
|
params: {
|
|
notificationId
|
|
}
|
|
});
|
|
if (json.seen) {
|
|
handleNotificationSee({
|
|
params: {
|
|
notificationId
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
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) {
|
|
return;
|
|
}
|
|
if (
|
|
generalSettingsStore.autoAcceptInviteRequests === 'All Favorites' &&
|
|
!favoriteStore.favoriteFriends.some(
|
|
(x) => x.id === ref.senderUserId
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
if (
|
|
generalSettingsStore.autoAcceptInviteRequests ===
|
|
'Selected Favorites' &&
|
|
!friendStore.localFavoriteFriends.has(ref.senderUserId)
|
|
) {
|
|
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(args) {
|
|
const { notificationId } = args.params;
|
|
removeFromArray(unseenNotifications.value, notificationId);
|
|
if (unseenNotifications.value.length === 0) {
|
|
uiStore.removeNotify('notification');
|
|
}
|
|
}
|
|
|
|
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} json
|
|
* @returns {object}
|
|
*/
|
|
function applyNotification(json) {
|
|
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;
|
|
}
|
|
if (ref.type === 'boop') {
|
|
ref.message = ref.title;
|
|
if (ref.details?.emojiId.startsWith('default_')) {
|
|
ref.details.imageUrl = ref.details.emojiId;
|
|
ref.message += ` ${ref.details.emojiId.replace('default_', '')}`;
|
|
} else {
|
|
ref.details.imageUrl = `${AppDebug.endpointDomain}/file/${ref.details.emojiId}/${ref.details.emojiVersion}`;
|
|
}
|
|
}
|
|
return ref;
|
|
}
|
|
|
|
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[i].type.includes('.')
|
|
) {
|
|
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
|
|
}
|
|
});
|
|
}
|
|
|
|
function handleNotificationV2(args) {
|
|
const json = args.json;
|
|
json.created_at = json.createdAt;
|
|
if (json.title && json.message) {
|
|
json.message = `${json.title}, ${json.message}`;
|
|
} else if (json.title) {
|
|
json.message = json.title;
|
|
}
|
|
handleNotification({
|
|
json,
|
|
params: {
|
|
notificationId: json.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
|
|
}
|
|
});
|
|
}
|
|
unseenNotifications.value = [];
|
|
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) {
|
|
json.created_at = json.createdAt;
|
|
if (json.title && json.message) {
|
|
json.message = `${json.title}, ${json.message}`;
|
|
} else if (json.title) {
|
|
json.message = json.title;
|
|
}
|
|
handleNotification({
|
|
json,
|
|
params: {
|
|
notificationId: json.id
|
|
}
|
|
});
|
|
}
|
|
|
|
unseenNotifications.value = [];
|
|
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
|
|
}
|
|
});
|
|
}
|
|
unseenNotifications.value = [];
|
|
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.value[notyId] &&
|
|
notyMap.value[notyId] >= noty.created_at
|
|
) {
|
|
return;
|
|
}
|
|
notyMap.value[notyId] = noty.created_at;
|
|
}
|
|
const bias = new Date(Date.now() - 60000).toJSON();
|
|
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) {
|
|
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(noty) {
|
|
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(noty) {
|
|
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;
|
|
notificationTable.value.data = await database.getNotifications();
|
|
refreshNotifications();
|
|
}
|
|
|
|
return {
|
|
notificationInitStatus,
|
|
notificationTable,
|
|
unseenNotifications,
|
|
isNotificationsLoading,
|
|
|
|
initNotifications,
|
|
expireNotification,
|
|
refreshNotifications,
|
|
queueNotificationNoty,
|
|
playNoty,
|
|
queueGameLogNoty,
|
|
queueFeedNoty,
|
|
queueFriendLogNoty,
|
|
queueModerationNoty,
|
|
handleNotificationAccept,
|
|
handleNotificationSee,
|
|
handlePipelineNotification,
|
|
handleNotificationV2Update,
|
|
handleNotificationHide,
|
|
handleNotification,
|
|
handleNotificationV2
|
|
};
|
|
});
|