Files
VRCX/src/stores/sharedFeed.js
2026-01-31 23:46:37 +09:00

424 lines
14 KiB
JavaScript

import { defineStore } from 'pinia';
import { ref } from 'vue';
import { watch } from 'vue';
import {
compareByCreatedAt,
getGroupName,
getWorldName
} from '../shared/utils';
import { database } from '../service/database';
import { useFriendStore } from './friend';
import { useInstanceStore } from './instance';
import { useLocationStore } from './location';
import { useModerationStore } from './moderation';
import { useNotificationStore } from './notification';
import { useNotificationsSettingsStore } from './settings/notifications';
import { useUserStore } from './user';
import { useWristOverlaySettingsStore } from './settings/wristOverlay';
import { watchState } from '../service/watchState';
export const useSharedFeedStore = defineStore('SharedFeed', () => {
const friendStore = useFriendStore();
const notificationsSettingsStore = useNotificationsSettingsStore();
const locationStore = useLocationStore();
const userStore = useUserStore();
const wristOverlaySettingsStore = useWristOverlaySettingsStore();
const instanceStore = useInstanceStore();
const moderationStore = useModerationStore();
const notificationStore = useNotificationStore();
const onPlayerJoining = ref([]);
async function rebuildOnPlayerJoining() {
let newOnPlayerJoining = [];
for (const ref of userStore.currentTravelers.values()) {
const isFavorite = friendStore.localFavoriteFriends.has(ref.id);
if (
locationStore.lastLocation.playerList.has(ref.id) ||
(notificationsSettingsStore.sharedFeedFilters.wrist
.OnPlayerJoining === 'VIP' &&
!isFavorite)
) {
continue;
}
if (ref.$location.tag === locationStore.lastLocation.location) {
const feedEntry = {
...ref,
isFavorite,
isFriend: true,
type: 'OnPlayerJoining'
};
newOnPlayerJoining.unshift(feedEntry);
continue;
}
const worldName = await getWorldName(ref.$location.worldId);
const groupName = await getGroupName(ref.$location.groupId);
const feedEntry = {
created_at: ref.created_at,
type: 'GPS',
userId: ref.id,
displayName: ref.displayName,
location: ref.$location.tag,
worldName,
groupName,
previousLocation: '',
isFavorite,
time: 0,
isFriend: true,
isTraveling: true
};
newOnPlayerJoining.unshift(feedEntry);
}
onPlayerJoining.value = newOnPlayerJoining;
sharedFeedData.value = sharedFeedData.value.filter(
(ctx) =>
!(
ctx.type === 'OnPlayerJoining' ||
(ctx.type === 'GPS' && ctx.isTraveling)
)
);
sharedFeedData.value.unshift(...onPlayerJoining.value);
if (sharedFeedData.value.length > maxEntries) {
sharedFeedData.value.splice(maxEntries);
}
sendSharedFeed();
}
watch(
() => userStore.currentTravelers,
() => rebuildOnPlayerJoining(),
{ deep: true }
);
watch(
() => watchState.isLoggedIn,
(isLoggedIn) => {
if (isLoggedIn) {
sharedFeedData.value = [];
loadSharedFeed();
}
},
{ flush: 'sync' }
);
const sharedFeedData = ref([]);
const maxEntries = 25;
async function loadSharedFeed() {
let newFeed = [];
const wristFilter = notificationsSettingsStore.sharedFeedFilters.wrist;
// run after fav and friendlist init
const vipList = Array.from(friendStore.localFavoriteFriends.values());
const friendList = Array.from(friendStore.friends.keys());
// Filters
const vipFilters = Object.keys(wristFilter).filter(
(key) => wristFilter[key] === 'VIP'
);
const friendsFilters = Object.keys(wristFilter).filter(
(key) => wristFilter[key] === 'Friends'
);
const everyoneFilters = Object.keys(wristFilter).filter(
(key) =>
wristFilter[key] === 'On' || wristFilter[key] === 'Everyone'
);
const everyoneAndFriendsFilters = Object.keys(wristFilter).filter(
(key) =>
wristFilter[key] === 'Friends' ||
wristFilter[key] === 'On' ||
wristFilter[key] === 'Everyone'
);
// Feed
if (vipFilters.length) {
const vipFeedRows = await database.lookupFeedDatabase(
vipFilters,
vipList,
maxEntries
);
newFeed = newFeed.concat(vipFeedRows);
}
if (everyoneAndFriendsFilters.length) {
const friendsFeedRows = await database.lookupFeedDatabase(
everyoneAndFriendsFilters,
[],
maxEntries
);
newFeed = newFeed.concat(friendsFeedRows);
}
// GameLog
if (vipFilters.length) {
const vipGameLogRows = await database.lookupGameLogDatabase(
'',
vipFilters,
vipList,
maxEntries
);
newFeed = newFeed.concat(vipGameLogRows);
}
if (friendsFilters.length) {
const friendsGameLogRows = await database.lookupGameLogDatabase(
'',
friendsFilters,
friendList,
maxEntries
);
newFeed = newFeed.concat(friendsGameLogRows);
}
if (everyoneFilters.length) {
const everyoneGameLogRows = await database.lookupGameLogDatabase(
'',
everyoneFilters,
[],
maxEntries
);
newFeed = newFeed.concat(everyoneGameLogRows);
}
// Notifications
if (vipFilters.length) {
const vipNotificationRows =
await database.lookupNotificationDatabase(
'',
vipFilters,
vipList,
maxEntries
);
newFeed = newFeed.concat(vipNotificationRows);
}
if (everyoneAndFriendsFilters.length) {
const friendsNotificationRows =
await database.lookupNotificationDatabase(
'',
everyoneAndFriendsFilters,
[],
maxEntries
);
newFeed = newFeed.concat(friendsNotificationRows);
}
// hide private worlds from feed
if (wristOverlaySettingsStore.hidePrivateFromFeed) {
newFeed = newFeed.filter(
(ctx) => !(ctx.type === 'GPS' && ctx.location === 'private')
);
}
// remove current user
newFeed = newFeed.filter(
(ctx) => ctx.userId !== userStore.currentUser.id
);
// FriendLog, Moderations Against (nope, not worth it)
newFeed.sort(compareByCreatedAt);
newFeed.splice(maxEntries);
for (const entry of newFeed) {
const userId = entry.userId || entry.senderUserId;
entry.isFriend = false;
entry.isFavorite = false;
entry.tagColour = '';
if (userId) {
entry.isFriend = friendStore.friends.has(userId);
entry.isFavorite = friendStore.localFavoriteFriends.has(userId);
entry.tagColour =
userStore.customUserTags.get(userId)?.colour ?? '';
}
// tack on instance names
const location = entry.location || entry.details?.location;
if (location) {
entry.instanceDisplayName =
await instanceStore.getInstanceName(location);
}
}
sharedFeedData.value = newFeed;
rebuildOnPlayerJoining(); // also sends updated feed
}
async function addEntry(data) {
const ctx = { ...data };
const userId = ctx.userId || ctx.senderUserId;
const wristFilter = notificationsSettingsStore.sharedFeedFilters.wrist;
if (userId === userStore.currentUser.id) {
return;
}
if (
wristOverlaySettingsStore.hidePrivateFromFeed &&
ctx.type === 'GPS' &&
ctx.location === 'private'
) {
return;
}
if (ctx.type === 'FriendRequest' || ctx.type === 'Avatar') {
return;
}
let isFriend = false;
let isFavorite = false;
let tagColour = '';
if (userId) {
isFriend = friendStore.friends.has(userId);
isFavorite = friendStore.localFavoriteFriends.has(userId);
tagColour = userStore.customUserTags.get(userId)?.colour ?? '';
}
// tack on instance names
const location = ctx.location || ctx.details?.location;
if (location) {
ctx.instanceDisplayName =
await instanceStore.getInstanceName(location);
}
// TODO: videoPlay come through before it gets video name
// TODO: On Location change remove OnPlayerLeft & OnPlayerJoined
{
// on Location change remove OnPlayerLeft
// if (ctx.type === 'LocationDestination') {
// if (!ctxTime) {
// ctxTime = Date.parse(ctx.created_at);
// }
// currentUserLeaveTime = ctxTime;
// const currentUserLeaveTimeOffset = currentUserLeaveTime + 5 * 1000;
// for (var k = w - 1; k > -1; k--) {
// var feedItem = wristArr[k];
// const feedItemTime = Date.parse(feedItem.created_at);
// if (
// (feedItem.type === 'OnPlayerLeft' ||
// feedItem.type === 'BlockedOnPlayerLeft' ||
// feedItem.type === 'MutedOnPlayerLeft') &&
// feedItemTime >= currentUserLeaveTime &&
// feedItemTime <= currentUserLeaveTimeOffset
// ) {
// wristArr.splice(k, 1);
// w--;
// }
// }
// }
// on Location change remove OnPlayerJoined
// if (ctx.type === 'Location') {
// if (!ctxTime) {
// ctxTime = Date.parse(ctx.created_at);
// }
// locationJoinTime = ctxTime;
// const locationJoinTimeOffset = locationJoinTime + 20 * 1000;
// for (let k = w - 1; k > -1; k--) {
// let feedItem = wristArr[k];
// const feedItemTime = Date.parse(feedItem.created_at);
// if (
// (feedItem.type === 'OnPlayerJoined' ||
// feedItem.type === 'BlockedOnPlayerJoined' ||
// feedItem.type === 'MutedOnPlayerJoined') &&
// feedItemTime >= locationJoinTime &&
// feedItemTime <= locationJoinTimeOffset
// ) {
// wristArr.splice(k, 1);
// w--;
// }
// }
// }
}
if (ctx.type === 'OnPlayerJoined' || ctx.type === 'OnPlayerLeft') {
moderationAgainstCheck({
...ctx,
isFavorite,
isFriend,
tagColour
});
}
if (
wristFilter[ctx.type] &&
(wristFilter[ctx.type] === 'On' ||
wristFilter[ctx.type] === 'Everyone' ||
(wristFilter[ctx.type] === 'Friends' && isFriend) ||
(wristFilter[ctx.type] === 'VIP' && isFavorite))
) {
addToSharedFeed({
...ctx,
isFavorite,
isFriend,
tagColour
});
}
}
function addToSharedFeed(ref) {
sharedFeedData.value.unshift(ref);
if (sharedFeedData.value.length > maxEntries) {
sharedFeedData.value.splice(maxEntries);
}
sendSharedFeed();
}
function moderationAgainstCheck(ctx) {
const wristFilter = notificationsSettingsStore.sharedFeedFilters.wrist;
// BlockedOnPlayerJoined, BlockedOnPlayerLeft, MutedOnPlayerJoined, MutedOnPlayerLeft
for (const ref of moderationStore.cachedPlayerModerations.values()) {
if (ref.sourceUserId !== ctx.userId) {
continue;
}
let type = '';
if (ref.type === 'block') {
type = `Blocked${ctx.type}`;
} else if (ref.type === 'mute') {
type = `Muted${ctx.type}`;
} else {
continue;
}
const entry = {
created_at: ctx.created_at,
type,
displayName: ctx.displayName,
userId: ctx.userId,
isFavorite: ctx.isFavorite,
isFriend: ctx.isFriend,
tagColour: ctx.tagColour
};
notificationStore.queueGameLogNoty(entry);
if (
wristFilter[type] &&
(wristFilter[type] === 'Everyone' ||
(wristFilter[type] === 'Friends' && ctx.isFriend) ||
(wristFilter[type] === 'VIP' && ctx.isFavorite))
) {
addToSharedFeed(entry);
}
}
}
function addTag(userId, colour) {
let changed = false;
for (const entry of sharedFeedData.value) {
if (entry.userId === userId) {
entry.tagColour = colour;
changed = true;
}
}
if (changed) {
sendSharedFeed();
}
}
async function sendSharedFeed() {
await AppApi.ExecuteVrOverlayFunction(
'wristFeedUpdate',
JSON.stringify(sharedFeedData.value)
);
}
return {
loadSharedFeed,
sendSharedFeed,
addEntry,
addTag,
sharedFeedData
};
});